diff options
Diffstat (limited to 'tests')
242 files changed, 14755 insertions, 472 deletions
diff --git a/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml b/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml index 3a0e2cc4..276be083 100644 --- a/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml +++ b/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml @@ -5,7 +5,7 @@ set interfaces ethernet eth0 address dhcp set interfaces ethernet eth0 speed auto set interfaces ethernet eth0 duplex auto - set interfaces ethernet eth1 address 192.0.2.1/24 + set interfaces ethernet eth1 set interfaces ethernet eth2 delete interfaces loopback lo vars: diff --git a/tests/integration/targets/vyos_config/tests/cli/backup.yaml b/tests/integration/targets/vyos_config/tests/cli/backup.yaml index adfa2ad7..3bcd5d35 100644 --- a/tests/integration/targets/vyos_config/tests/cli/backup.yaml +++ b/tests/integration/targets/vyos_config/tests/cli/backup.yaml @@ -2,17 +2,18 @@ - debug: msg="START vyos/backup.yaml on connection={{ ansible_connection }}" - name: collect any backup files - find: + ansible.builtin.find: paths: "{{ role_path }}/backup" pattern: "{{ inventory_hostname_short }}_config*" register: backup_files connection: local - name: delete backup files - file: + ansible.builtin.file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.files|default([]) }}" + connection: local - name: take configure backup register: result @@ -24,7 +25,7 @@ - result.changed == true - name: collect any backup files - find: + ansible.builtin.find: paths: "{{ role_path }}/backup" pattern: "{{ inventory_hostname_short }}_config*" register: backup_files @@ -35,13 +36,14 @@ - backup_files.files is defined - name: delete configurable backup file path - file: + ansible.builtin.file: path: "{{ item }}" state: absent with_items: - "{{ role_path }}/backup_test_dir/" - "{{ role_path }}/backup/backup.cfg" run_once: true + connection: local - name: take configuration backup in custom filename and directory path register: result @@ -56,7 +58,7 @@ - result.changed == true - name: check if the backup file-1 exist - find: + ansible.builtin.find: paths: "{{ role_path }}/backup_test_dir/{{ inventory_hostname_short }}/backup.cfg" register: backup_file connection: local @@ -77,7 +79,7 @@ - result.changed == true - name: check if the backup file-2 exist - find: + ansible.builtin.find: paths: "{{ role_path }}/backup/backup.cfg" register: backup_file connection: local @@ -98,7 +100,7 @@ - result.changed == true - name: check if the backup file-3 exist - find: + ansible.builtin.find: paths: "{{ role_path }}/backup_test_dir/{{ inventory_hostname_short }}" pattern: "{{ inventory_hostname_short }}_config*" register: backup_file diff --git a/tests/integration/targets/vyos_config/tests/cli/check_config.yaml b/tests/integration/targets/vyos_config/tests/cli/check_config.yaml index 8e2e8372..a03a3115 100644 --- a/tests/integration/targets/vyos_config/tests/cli/check_config.yaml +++ b/tests/integration/targets/vyos_config/tests/cli/check_config.yaml @@ -41,17 +41,71 @@ that: - result.changed == false -- name: check multiple line config filter is working - register: result +- name: setup- ensure test login accounts are not present vyos.vyos.vyos_config: lines: - - set system login user esa full-name 'ESA admin' - - set system login user esa authentication encrypted-password '!abc!' - - set system login user vyos full-name 'VyOS admin' - - set system login user vyos authentication encrypted-password 'abc' + - delete system login user testesa + - delete system login user testadmin + - delete system login user testjohn + match: none + ignore_errors: true -- assert: - that: - - result.filtered|length == 2 +- block: + - name: check multiple line config filter is working + register: result + vyos.vyos.vyos_config: + lines: + - set system login user testesa full-name 'ESA admin' + - set system login user testesa authentication encrypted-password '!abc!' + - set system login user testadmin full-name 'Test admin' + - set system login user testadmin authentication encrypted-password 'abc' + - set system login user testjohn full-name 'John' + - set system login user testjohn authentication plaintext-password 'xyz' + + - assert: + that: + - result.filtered|length == 2 + + - name: check multiple line config filter is working + register: result + vyos.vyos.vyos_config: + allow_password_change: none + lines: + - set system login user testesa full-name 'ESA admin' + - set system login user testesa authentication encrypted-password '!abc!' + - set system login user testadmin full-name 'Test admin' + - set system login user testadmin authentication encrypted-password 'abc' + - set system login user testjohn full-name 'John' + - set system login user testjohn authentication plaintext-password 'xyz' + + - assert: + that: + - result.filtered|length == 3 + + - name: check multiple line config filter is working + register: result + vyos.vyos.vyos_config: + allow_password_change: all + lines: + - set system login user testesa full-name 'ESA admin' + - set system login user testesa authentication encrypted-password '$5$jR0jeXg3jHzSN7k3$33fO1qz3CVzsWJhxksLE7.KTIizYWGAMsd6LH9ItJmB' + - set system login user testadmin full-name 'Test admin' + - set system login user testadmin authentication encrypted-password '$5$YVhfNdmQbKQpDROb$N1b2nKh69wvrtQu7JwiIp/IsWlLHU8QCDcNGVaRWh6.' + - set system login user testjohn full-name 'John' + - set system login user testjohn authentication plaintext-password 'xyz' + + - assert: + that: + - result.filtered|length == 0 + + always: + - name: cleanup test login accounts created by password filter checks + vyos.vyos.vyos_config: + lines: + - delete system login user testesa + - delete system login user testadmin + - delete system login user testjohn + match: none + ignore_errors: true - debug: msg="END cli/config_check.yaml on connection={{ ansible_connection }}" diff --git a/tests/integration/targets/vyos_config/tests/cli/confirm.yaml b/tests/integration/targets/vyos_config/tests/cli/confirm.yaml new file mode 100644 index 00000000..a7a3cff4 --- /dev/null +++ b/tests/integration/targets/vyos_config/tests/cli/confirm.yaml @@ -0,0 +1,55 @@ +--- +- debug: msg="START cli/confirm.yaml on connection={{ ansible_connection }}" + +- name: setup + vyos.vyos.vyos_config: + lines: set system host-name {{ inventory_hostname_short }} + match: none + +- name: configure with confirm (manual) + register: result + vyos.vyos.vyos_config: + lines: set system host-name foo + comment: confirm manual test + confirm: manual + confirm_timeout: 1 + +- assert: + that: + - result.changed == true + - "'set system host-name foo' in result.commands" + +- name: verify hostname changed to foo + register: hostname_after + vyos.vyos.vyos_command: + commands: show host name + +- assert: + that: + - "'foo' in hostname_after.stdout[0]" + +- name: pause to allow confirm timeout to elapse and device to revert/reboot + pause: + seconds: 75 + +- name: reset connection to avoid stale ssh channel after device reboot + meta: reset_connection + +- name: wait until config auto-reverts (no confirmation) + register: hostname_reverted + vyos.vyos.vyos_command: + commands: show host name + retries: 18 + delay: 10 + until: hostname_reverted is succeeded and inventory_hostname_short in hostname_reverted.stdout[0] + ignore_unreachable: true + +- name: reset connection after device reboot recovery + meta: reset_connection + +- name: teardown + vyos.vyos.vyos_config: + lines: set system host-name {{ inventory_hostname_short }} + match: none + +- debug: msg="END cli/confirm.yaml on connection={{ ansible_connection }}" diff --git a/tests/integration/targets/vyos_config/tests/cli/save.yaml b/tests/integration/targets/vyos_config/tests/cli/save.yaml index e8a9035b..b39ef957 100644 --- a/tests/integration/targets/vyos_config/tests/cli/save.yaml +++ b/tests/integration/targets/vyos_config/tests/cli/save.yaml @@ -6,7 +6,7 @@ lines: set system host-name {{ inventory_hostname_short }} match: none -- name: configure hostaname and save +- name: configure hostname and save register: result vyos.vyos.vyos_config: lines: set system host-name foo @@ -17,7 +17,7 @@ - result.changed == true - "'set system host-name foo' in result.commands" -- name: configure hostaname and don't save +- name: configure hostname and don't save register: result vyos.vyos.vyos_config: lines: set system host-name bar diff --git a/tests/integration/targets/vyos_config/tests/cli/simple.yaml b/tests/integration/targets/vyos_config/tests/cli/simple.yaml index 3db59270..1559fa2b 100644 --- a/tests/integration/targets/vyos_config/tests/cli/simple.yaml +++ b/tests/integration/targets/vyos_config/tests/cli/simple.yaml @@ -25,6 +25,17 @@ that: - result.changed == false +- name: configure simple config command while match = 'none' + register: result + vyos.vyos.vyos_config: + lines: set system host-name foo + match: none + +- assert: + that: + - result.changed == true + - "'set system host-name foo' in result.commands" + - name: Delete services vyos.vyos.vyos_config: &id001 lines: diff --git a/tests/integration/targets/vyos_config/tests/cli_config/cli_backup.yaml b/tests/integration/targets/vyos_config/tests/cli_config/cli_backup.yaml index 03450c31..681a22ee 100644 --- a/tests/integration/targets/vyos_config/tests/cli_config/cli_backup.yaml +++ b/tests/integration/targets/vyos_config/tests/cli_config/cli_backup.yaml @@ -2,26 +2,28 @@ - debug: msg="END cli_config/backup.yaml on connection={{ ansible_connection }}" - name: delete configurable backup file path - file: + ansible.builtin.file: path: "{{ item }}" state: absent with_items: - "{{ role_path }}/backup_test_dir/" - "{{ role_path }}/backup/backup.cfg" run_once: true + connection: local - name: collect any backup files - find: + ansible.builtin.find: paths: "{{ role_path }}/backup" pattern: "{{ inventory_hostname_short }}_config*" register: backup_files connection: local - name: delete backup files - file: + ansible.builtin.file: path: "{{ item.path }}" state: absent with_items: "{{backup_files.files|default([])}}" + connection: local - name: take config backup register: result @@ -33,7 +35,7 @@ - result.changed == true - name: collect any backup files - find: + ansible.builtin.find: paths: "{{ role_path }}/backup" pattern: "{{ inventory_hostname_short }}_config*" register: backup_files @@ -56,7 +58,7 @@ - result.changed == true - name: check if the backup file-1 exist - find: + ansible.builtin.find: paths: "{{ role_path }}/backup_test_dir/{{ inventory_hostname_short }}/backup.cfg" register: backup_file connection: local @@ -77,7 +79,7 @@ - result.changed == true - name: check if the backup file-2 exist - find: + ansible.builtin.find: paths: "{{ role_path }}/backup/backup.cfg" register: backup_file connection: local @@ -98,7 +100,7 @@ - result.changed == true - name: check if the backup file-3 exist - find: + ansible.builtin.find: paths: "{{ role_path }}/backup_test_dir/{{ inventory_hostname_short }}" pattern: "{{ inventory_hostname_short }}_config*" register: backup_file diff --git a/tests/integration/targets/vyos_file/aliases b/tests/integration/targets/vyos_file/aliases new file mode 100644 index 00000000..8071e1f7 --- /dev/null +++ b/tests/integration/targets/vyos_file/aliases @@ -0,0 +1 @@ +shippable/vyos/group1 diff --git a/tests/integration/targets/vyos_file/defaults/main.yaml b/tests/integration/targets/vyos_file/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_file/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_file/tasks/cli.yaml b/tests/integration/targets/vyos_file/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_file/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 + register: test_cases + delegate_to: localhost + +- 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_file/tasks/main.yaml b/tests/integration/targets/vyos_file/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_file/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_file/tests/cli/_remove_files.yaml b/tests/integration/targets/vyos_file/tests/cli/_remove_files.yaml new file mode 100644 index 00000000..8ab17d94 --- /dev/null +++ b/tests/integration/targets/vyos_file/tests/cli/_remove_files.yaml @@ -0,0 +1,8 @@ +--- +- name: reset test directories to a known-absent baseline + vyos.vyos.vyos_command: + commands: + - "sudo rm -rf {{ test_dir }}" + - "sudo rm -rf {{ test_dir_tmp }}" + - "sudo rm -rf {{ test_dir_protected }}" + ignore_errors: true diff --git a/tests/integration/targets/vyos_file/tests/cli/basic.yaml b/tests/integration/targets/vyos_file/tests/cli/basic.yaml new file mode 100644 index 00000000..271739c5 --- /dev/null +++ b/tests/integration/targets/vyos_file/tests/cli/basic.yaml @@ -0,0 +1,239 @@ +--- +- debug: + msg: START vyos_file basic integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_files.yaml + +- block: + - name: 1. create directory + vyos.vyos.vyos_file: + dest: "{{ test_dir }}" + state: present + owner: "{{ test_owner }}" + group: "{{ test_group }}" + mode: "0750" + register: t1 + + - assert: + that: + - t1.changed + - "'state' in t1.diff_fields" + - "'owner' in t1.diff_fields" + - "'group' in t1.diff_fields" + - "'mode' in t1.diff_fields" + + - name: 2. re-run same task — must be a no-op + vyos.vyos.vyos_file: + dest: "{{ test_dir }}" + state: present + owner: "{{ test_owner }}" + group: "{{ test_group }}" + mode: "0750" + register: t2 + + - assert: + that: + - not t2.changed + - t2.diff_fields == [] + + - name: 3. push inline content + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + content: "integration test content\n" + owner: "{{ test_owner }}" + group: "{{ test_group }}" + mode: "0600" + register: t3 + + - assert: + that: + - t3.changed + - "'content' in t3.diff_fields" + + - name: 3b. re-run identical content push — must be a no-op + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + content: "integration test content\n" + owner: "{{ test_owner }}" + group: "{{ test_group }}" + mode: "0600" + register: t3b + + - assert: + that: + - not t3b.changed + - t3b.diff_fields == [] + + - name: 4. change mode only, non-canonical string + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + mode: "0640" + register: t4 + + - assert: + that: + - t4.changed + - t4.diff_fields == ['mode'] + + - name: 5. re-assert same mode, non-zero-padded — mode string normalization + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + mode: "640" + register: t5 + + - assert: + that: + - not t5.changed + - t5.diff_fields == [] + + - name: 6. check_mode dry run must report a diff without converging + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + mode: "0777" + check_mode: true + register: t6 + + - assert: + that: + - t6.changed + - t6.diff_fields == ['mode'] + + - name: verify check_mode did not actually touch the file + vyos.vyos.vyos_command: + commands: + - "sudo stat --format='%a' {{ test_dir }}/hello.txt" + register: post_check_mode_stat + + - assert: + that: + - "'640' in post_check_mode_stat.stdout[0]" + fail_msg: "check_mode leaked a real converge — mode changed despite check_mode:true" + + - name: 7. remove file + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + state: absent + register: t7 + + - assert: + that: + - t7.changed + - t7.diff_fields == ['state'] + + - name: 8. remove again — must be a no-op + vyos.vyos.vyos_file: + dest: "{{ test_dir }}/hello.txt" + state: absent + register: t8 + + - assert: + that: + - not t8.changed + - t8.diff_fields == [] + + - name: 9a. explicit setgid request converges on a path with no prior special bits (/tmp, outside VyOS's own /config/auth enforcement) + vyos.vyos.vyos_file: + dest: "{{ test_dir_tmp }}" + state: present + owner: "{{ test_owner }}" + group: "{{ test_group }}" + mode: "2750" + register: t9a + + - assert: + that: + - t9a.changed + - "'mode' in t9a.diff_fields" + + - name: 9b. re-run identical explicit request — must be a no-op (idempotency) + vyos.vyos.vyos_file: + dest: "{{ test_dir_tmp }}" + mode: "2750" + register: t9b + + - assert: + that: + - not t9b.changed + + - name: 10a. pre-stage setgid via raw command, outside this module's control + vyos.vyos.vyos_command: + commands: + - "sudo chmod 2770 {{ test_dir_tmp }}" + + - name: 10b. implicit mode request must preserve the pre-existing setgid bit + vyos.vyos.vyos_file: + dest: "{{ test_dir_tmp }}" + mode: "0640" + register: t10 + + - assert: + that: + - t10.changed + - t10.diff_fields == ['mode'] + + - name: verify setgid survived an implicit-mode rwx change (the real regression this guards against) + vyos.vyos.vyos_command: + commands: + - "sudo stat --format='%a' {{ test_dir_tmp }}" + register: post_implicit_mode_stat + + - assert: + that: + - "'2640' in post_implicit_mode_stat.stdout[0]" + fail_msg: >- + implicit mode request cleared the pre-existing setgid bit — + expected 2640 (setgid preserved, rwx changed to 640), got + {{ post_implicit_mode_stat.stdout[0] }} + + - name: cleanup /tmp test path + vyos.vyos.vyos_file: + dest: "{{ test_dir_tmp }}" + state: absent + + - name: 11a. pre-stage a genuinely protected directory (root:root, 0700 — vyos has zero direct access) + vyos.vyos.vyos_command: + commands: + - "sudo mkdir -p {{ test_dir_protected }}" + - "sudo chown root:root {{ test_dir_protected }}" + - "sudo chmod 0700 {{ test_dir_protected }}" + + - name: 11b. content push to a protected destination must succeed via staged transfer + sudo mv + vyos.vyos.vyos_file: + dest: "{{ test_dir_protected }}/secret.txt" + content: "content pushed to a root-only directory\n" + owner: root + mode: "0600" + register: t11 + + - assert: + that: + - t11.changed + - "'content' in t11.diff_fields" + fail_msg: >- + content push to a protected (root-only) destination failed — + the staging-then-sudo-mv approach should succeed here even + though a direct SCP write (no become) would be rejected + + - name: verify the file actually landed with correct content (root reads it, vyos cannot) + vyos.vyos.vyos_command: + commands: + - "sudo cat {{ test_dir_protected }}/secret.txt" + register: protected_content_check + + - assert: + that: + - "'content pushed to a root-only directory' in protected_content_check.stdout[0]" + + - name: 11c. re-run identical push to protected destination — must be a no-op (idempotency under become) + vyos.vyos.vyos_file: + dest: "{{ test_dir_protected }}/secret.txt" + content: "content pushed to a root-only directory\n" + owner: root + mode: "0600" + register: t11c + + - assert: + that: + - not t11c.changed + always: + - include_tasks: _remove_files.yaml diff --git a/tests/integration/targets/vyos_file/vars/main.yaml b/tests/integration/targets/vyos_file/vars/main.yaml new file mode 100644 index 00000000..e39f20cd --- /dev/null +++ b/tests/integration/targets/vyos_file/vars/main.yaml @@ -0,0 +1,6 @@ +--- +test_dir: /config/auth/_vyos_file_test +test_dir_tmp: /tmp/_vyos_file_test_bits +test_dir_protected: /etc/_vyos_file_test_protected +test_group: vyattacfg +test_owner: vyos diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml b/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml index 2588b194..45bc2e18 100644 --- a/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml +++ b/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml @@ -8,6 +8,7 @@ - name: debug vyos_facts debug: var: vyos_facts + when: vyos_facts is not skipped - name: pull version from facts set_fact: @@ -19,10 +20,20 @@ vyos_version: "{{ vyos_version }}.0" when: vyos_version.count('.') == 1 -- name: include correct vars +- name: report resolved VyOS version + debug: + msg: "Using VyOS version {{ vyos_version }} for this testcase" + +- name: include correct vars (pre-1.4.0) include_vars: pre-v1_4.yaml when: vyos_version is version('1.4.0', '<', version_type='semver') -- name: include correct vars +- name: include correct vars (1.4.x) include_vars: v1_4.yaml - when: vyos_version is version('1.4.0', '>=', version_type='semver') + when: >- + vyos_version is version('1.4.0', '>=', version_type='semver') and + vyos_version is version('1.5.0', '<', version_type='semver') + +- name: include correct vars (1.5.0+) + include_vars: v1_5.yaml + when: vyos_version is version('1.5.0', '>=', version_type='semver') diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg index 41435780..882d9aaa 100644 --- a/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg +++ b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg @@ -16,3 +16,6 @@ set firewall global-options state-policy established log 'enable' set firewall global-options state-policy invalid action 'reject' set firewall global-options syn-cookies 'enable' set firewall global-options twa-hazards-protection 'enable' +set firewall zone ZONE-TEST interface 'eth0.1234' +set firewall zone ZONE-TEST description 'zone-test test description' +set firewall zone ZONE-TEST default-action 'drop' diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg new file mode 100644 index 00000000..88553c00 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg @@ -0,0 +1,21 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'enable' +set firewall group address-group MGMT-HOSTS address '192.0.1.1' +set firewall group address-group MGMT-HOSTS address '192.0.1.3' +set firewall group address-group MGMT-HOSTS address '192.0.1.5' +set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' +set firewall group network-group MGMT description 'This group has the Management network addresses' +set firewall group network-group MGMT network '192.0.1.0/24' +set firewall global-options ip-src-route 'enable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'strict' +set firewall global-options state-policy established action 'accept' +set firewall global-options state-policy established log 'enable' +set firewall global-options state-policy invalid action 'reject' +set firewall global-options syn-cookies 'enable' +set firewall global-options twa-hazards-protection 'enable' +set firewall zone ZONE-TEST member interface 'eth0.1234' +set firewall zone ZONE-TEST description 'zone-test test description' +set firewall zone ZONE-TEST default-action 'drop' diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/deleted.yaml b/tests/integration/targets/vyos_firewall_global/tests/cli/deleted.yaml index 19fc83de..abcf597c 100644 --- a/tests/integration/targets/vyos_firewall_global/tests/cli/deleted.yaml +++ b/tests/integration/targets/vyos_firewall_global/tests/cli/deleted.yaml @@ -10,11 +10,16 @@ vyos.vyos.vyos_firewall_global: &id001 config: state: deleted + diff: true - name: Assert that the before dicts were correctly generated assert: that: - "{{ populate == result['before'] }}" + - item in result.diff.prepared + loop: "{{ deleted_diff }}" + loop_control: + loop_var: item - name: Assert that the correct set of commands were generated assert: @@ -29,12 +34,14 @@ - name: Delete attributes of given interfaces (IDEMPOTENT) register: result vyos.vyos.vyos_firewall_global: *id001 + diff: true - name: Assert that the previous task was idempotent assert: that: - result.changed == false - result.commands|length == 0 + - result['diff'] is not defined - name: Assert that the before dicts were correctly generated assert: diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/merged.yaml b/tests/integration/targets/vyos_firewall_global/tests/cli/merged.yaml index a538476a..d40d37c9 100644 --- a/tests/integration/targets/vyos_firewall_global/tests/cli/merged.yaml +++ b/tests/integration/targets/vyos_firewall_global/tests/cli/merged.yaml @@ -2,6 +2,8 @@ - debug: msg: START vyos_firewall_global merged integration tests on connection={{ ansible_connection }} +- include_tasks: _get_version.yaml + - include_tasks: _remove_config.yaml - block: @@ -28,15 +30,33 @@ - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) register: result vyos.vyos.vyos_firewall_global: *id001 + diff: true - name: Assert that the previous task was idempotent assert: that: - result['changed'] == false + - result['diff'] is not defined - name: Assert that before dicts were correctly generated assert: that: - "{{ merged['after'] == result['before'] }}" + + - name: Prepare device configurationsfor diff mode test + register: result + vyos.vyos.vyos_firewall_global: + config: "{{ merged['diff_config'] }}" + state: merged + diff: true + + - name: Assert that correct diff of commands were generated + assert: + that: + - item in result.diff.prepared + loop: "{{ merged_diff }}" + loop_control: + loop_var: item + always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/replaced.yaml b/tests/integration/targets/vyos_firewall_global/tests/cli/replaced.yaml index 4c7b4279..26c94b30 100644 --- a/tests/integration/targets/vyos_firewall_global/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_firewall_global/tests/cli/replaced.yaml @@ -29,15 +29,33 @@ - name: Replace device configurations of listed firewall with provided configurarions (IDEMPOTENT) register: result vyos.vyos.vyos_firewall_global: *id001 + diff: true - name: Assert that task was idempotent assert: that: - result['changed'] == false + - result['diff'] is not defined - name: Assert that before dict is correctly generated assert: that: - "{{ replaced['after'] == result['before'] }}" + + - name: Replace device configurations and test diff mode + register: result + vyos.vyos.vyos_firewall_global: + config: "{{ replaced['diff_config'] }}" + state: replaced + diff: true + + - name: Assert that correct diff of commands were generated + assert: + that: + - item in result.diff.prepared + loop: "{{ replaced_diff }}" + loop_control: + loop_var: item + always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_global/vars/main.yaml b/tests/integration/targets/vyos_firewall_global/vars/main.yaml index 363cc9e6..94bdca87 100644 --- a/tests/integration/targets/vyos_firewall_global/vars/main.yaml +++ b/tests/integration/targets/vyos_firewall_global/vars/main.yaml @@ -37,6 +37,12 @@ merged: connection_type: invalid twa_hazards_protection: true validation: strict + zone: + - name: ZONE-TEST + default_action: drop + description: zone-test test description + interfaces: + - eth0.1234 config: validation: strict log_martians: true @@ -70,6 +76,45 @@ merged: description: This group has the Management network addresses members: - address: 192.0.1.0/24 + zone: + - name: ZONE-TEST + description: zone-test test description + interfaces: + - eth0.1234 + + diff_config: + validation: strict + log_martians: true + syn_cookies: true + twa_hazards_protection: true + ping: + all: true + broadcast: true + state_policy: + - connection_type: established + action: accept + log: true + - connection_type: invalid + action: reject + route_redirects: + - afi: ipv4 + ip_src_route: true + icmp_redirects: + send: true + receive: false + group: + address_group: + - name: MGMT-HOSTS + description: This group has the Management hosts address list + members: + - address: 192.0.1.1 + - address: 192.0.1.3 + - address: 192.0.1.5 + network_group: + - name: MGMT + description: This group has the Management network addresses + members: + - address: 1.1.1.1/32 populate: validation: strict @@ -106,7 +151,12 @@ populate: members: - address: 192.0.1.0/24 afi: ipv4 - + zone: + - name: ZONE-TEST + description: zone-test test description + interfaces: + - eth0.1234 + default_action: drop replaced: commands: "{{ replaced_commands }}" after: @@ -150,6 +200,16 @@ replaced: syn_cookies: true twa_hazards_protection: true validation: strict + zone: + - name: FZP-2 + default_action: reject + default_log: true + description: This is the Firewall zone fzp2 + interfaces: + - eth2 + - lo + intra_zone_filtering: + action: accept config: validation: strict log_martians: true @@ -188,6 +248,54 @@ replaced: description: This group has the Management network addresses members: - address: 192.0.1.0/24 + zone: + - name: FZP-2 + default_action: reject + default_log: true + description: This is the Firewall zone fzp2 + interfaces: + - eth2 + - lo + intra_zone_filtering: + action: accept + diff_config: + validation: strict + log_martians: true + syn_cookies: true + twa_hazards_protection: true + ping: + all: true + broadcast: true + state_policy: + - connection_type: established + action: accept + log: true + - connection_type: invalid + action: reject + route_redirects: + - afi: ipv4 + ip_src_route: true + icmp_redirects: + send: true + receive: false + group: + address_group: + - name: SALES-HOSTS + description: Sales office hosts address list + members: + - address: 192.0.2.1 + - address: 192.0.2.2 + - address: 192.0.2.3 + - name: ENG-HOSTS + description: Sales office hosts address list + members: + - address: 192.0.3.1 + - address: 192.0.3.2 + network_group: + - name: MGMT + description: This group has the Management network addresses + members: + - address: 1.1.1.1/32 rendered: commands: "{{ rendered_commands }}" @@ -229,6 +337,12 @@ rendered: description: This group has the Management network addresses members: - address: 192.0.1.0/24 + zone: + - name: ZONE-TEST + description: zone-test test description + interfaces: + - eth0.1234 + default_action: drop deleted: commands: "{{ deleted_commands }}" @@ -270,6 +384,16 @@ round_trip: members: - address: 192.0.1.0/24 afi: ipv4 + zone: + - name: FZP-2 + default_action: reject + default_log: true + description: This is the Firewall zone fzp2 + interfaces: + - eth2 + - lo + intra_zone_filtering: + action: accept forward_config: validation: strict log_martians: true @@ -303,6 +427,16 @@ round_trip: description: This group has the Management network addresses members: - address: 192.0.1.0/24 + zone: + - name: FZP-2 + default_action: reject + default_log: true + description: This is the Firewall zone fzp2 + interfaces: + - eth2 + - lo + intra_zone_filtering: + action: accept revert_config: validation: strict log_martians: false diff --git a/tests/integration/targets/vyos_firewall_global/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_firewall_global/vars/pre-v1_4.yaml index db293451..581d4d57 100644 --- a/tests/integration/targets/vyos_firewall_global/vars/pre-v1_4.yaml +++ b/tests/integration/targets/vyos_firewall_global/vars/pre-v1_4.yaml @@ -83,3 +83,13 @@ deleted_commands: - "delete firewall" parsed_config_file: "_parsed_config_1_3.cfg" + +replaced_diff: + - "+network 1.1.1.1/32" + - "-network 192.0.1.0/24" + +merged_diff: + - "+network 1.1.1.1/32" + +deleted_diff: + - "- network 192.0.1.0/24" diff --git a/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml b/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml index d1ee6f2b..fffa93d8 100644 --- a/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml +++ b/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml @@ -20,6 +20,9 @@ merged_commands: - set firewall global-options twa-hazards-protection 'enable' - set firewall global-options syn-cookies 'enable' - set firewall global-options source-validation 'strict' + - set firewall zone ZONE-TEST description 'zone-test test description' + - set firewall zone ZONE-TEST interface eth0.1234 + - set firewall zone ZONE-TEST default-action 'drop' populate_commands: - set firewall global-options all-ping 'enable' @@ -40,9 +43,12 @@ populate_commands: - set firewall global-options state-policy invalid action 'reject' - set firewall global-options syn-cookies 'enable' - set firewall global-options twa-hazards-protection 'enable' + - set firewall zone ZONE-TEST interface 'eth0.1234' + - set firewall zone ZONE-TEST description 'zone-test test description' replaced_commands: - delete firewall group address-group MGMT-HOSTS + - delete firewall zone ZONE-TEST - set firewall group address-group SALES-HOSTS address 192.0.2.1 - set firewall group address-group SALES-HOSTS address 192.0.2.2 - set firewall group address-group SALES-HOSTS address 192.0.2.3 @@ -52,6 +58,12 @@ replaced_commands: - set firewall group address-group ENG-HOSTS address 192.0.3.2 - set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' - set firewall group address-group ENG-HOSTS + - set firewall zone FZP-2 default-action 'reject' + - set firewall zone FZP-2 default-log + - set firewall zone FZP-2 description 'This is the Firewall zone fzp2' + - set firewall zone FZP-2 interface eth2 + - set firewall zone FZP-2 interface lo + - set firewall zone FZP-2 intra-zone-filtering action accept rendered_commands: - set firewall group address-group SALES-HOSTS address 192.0.2.1 @@ -78,8 +90,21 @@ rendered_commands: - set firewall global-options twa-hazards-protection 'enable' - set firewall global-options syn-cookies 'enable' - set firewall global-options source-validation 'strict' + - set firewall zone ZONE-TEST interface eth0.1234 + - set firewall zone ZONE-TEST description 'zone-test test description' + - set firewall zone ZONE-TEST default-action 'drop' deleted_commands: - "delete firewall" parsed_config_file: "_parsed_config_1_4.cfg" + +replaced_diff: + - '+ network "1.1.1.1/32"' + - '- network "192.0.1.0/24"' + +merged_diff: + - '+ network "1.1.1.1/32"' + +deleted_diff: + - '- network "192.0.1.0/24"' diff --git a/tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml b/tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml new file mode 100644 index 00000000..d32eacc7 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml @@ -0,0 +1,110 @@ +--- +merged_commands: + - set firewall group address-group MGMT-HOSTS address 192.0.1.1 + - set firewall group address-group MGMT-HOSTS address 192.0.1.3 + - set firewall group address-group MGMT-HOSTS address 192.0.1.5 + - set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' + - set firewall group address-group MGMT-HOSTS + - set firewall group network-group MGMT network 192.0.1.0/24 + - set firewall group network-group MGMT description 'This group has the Management network addresses' + - set firewall group network-group MGMT + - set firewall global-options ip-src-route 'enable' + - set firewall global-options receive-redirects 'disable' + - set firewall global-options send-redirects 'enable' + - set firewall global-options state-policy established action 'accept' + - set firewall global-options state-policy established log + - set firewall global-options state-policy invalid action 'reject' + - set firewall global-options broadcast-ping 'enable' + - set firewall global-options all-ping 'enable' + - set firewall global-options log-martians 'enable' + - set firewall global-options twa-hazards-protection 'enable' + - set firewall global-options syn-cookies 'enable' + - set firewall global-options source-validation 'strict' + - set firewall zone ZONE-TEST description 'zone-test test description' + - set firewall zone ZONE-TEST member interface eth0.1234 + - set firewall zone ZONE-TEST default-action 'drop' + +populate_commands: + - set firewall global-options all-ping 'enable' + - set firewall global-options broadcast-ping 'enable' + - set firewall group address-group MGMT-HOSTS address '192.0.1.1' + - set firewall group address-group MGMT-HOSTS address '192.0.1.3' + - set firewall group address-group MGMT-HOSTS address '192.0.1.5' + - set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' + - set firewall group network-group MGMT description 'This group has the Management network addresses' + - set firewall group network-group MGMT network '192.0.1.0/24' + - set firewall global-options ip-src-route 'enable' + - set firewall global-options log-martians 'enable' + - set firewall global-options receive-redirects 'disable' + - set firewall global-options send-redirects 'enable' + - set firewall global-options source-validation 'strict' + - set firewall global-options state-policy established action 'accept' + - set firewall global-options state-policy established log + - set firewall global-options state-policy invalid action 'reject' + - set firewall global-options syn-cookies 'enable' + - set firewall global-options twa-hazards-protection 'enable' + - set firewall zone ZONE-TEST member interface 'eth0.1234' + - set firewall zone ZONE-TEST description 'zone-test test description' + +replaced_commands: + - delete firewall group address-group MGMT-HOSTS + - delete firewall zone ZONE-TEST + - set firewall group address-group SALES-HOSTS address 192.0.2.1 + - set firewall group address-group SALES-HOSTS address 192.0.2.2 + - set firewall group address-group SALES-HOSTS address 192.0.2.3 + - set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' + - set firewall group address-group SALES-HOSTS + - set firewall group address-group ENG-HOSTS address 192.0.3.1 + - set firewall group address-group ENG-HOSTS address 192.0.3.2 + - set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' + - set firewall group address-group ENG-HOSTS + - set firewall zone FZP-2 default-action 'reject' + - set firewall zone FZP-2 default-log + - set firewall zone FZP-2 description 'This is the Firewall zone fzp2' + - set firewall zone FZP-2 member interface eth2 + - set firewall zone FZP-2 member interface lo + - set firewall zone FZP-2 intra-zone-filtering action accept + +rendered_commands: + - set firewall group address-group SALES-HOSTS address 192.0.2.1 + - set firewall group address-group SALES-HOSTS address 192.0.2.2 + - set firewall group address-group SALES-HOSTS address 192.0.2.3 + - set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' + - set firewall group address-group SALES-HOSTS + - set firewall group address-group ENG-HOSTS address 192.0.3.1 + - set firewall group address-group ENG-HOSTS address 192.0.3.2 + - set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' + - set firewall group address-group ENG-HOSTS + - set firewall group network-group MGMT network 192.0.1.0/24 + - set firewall group network-group MGMT description 'This group has the Management network addresses' + - set firewall group network-group MGMT + - set firewall global-options ip-src-route 'enable' + - set firewall global-options receive-redirects 'disable' + - set firewall global-options send-redirects 'enable' + - set firewall global-options state-policy established action 'accept' + - set firewall global-options state-policy established log + - set firewall global-options state-policy invalid action 'reject' + - set firewall global-options broadcast-ping 'enable' + - set firewall global-options all-ping 'enable' + - set firewall global-options log-martians 'enable' + - set firewall global-options twa-hazards-protection 'enable' + - set firewall global-options syn-cookies 'enable' + - set firewall global-options source-validation 'strict' + - set firewall zone ZONE-TEST member interface eth0.1234 + - set firewall zone ZONE-TEST description 'zone-test test description' + - set firewall zone ZONE-TEST default-action 'drop' + +deleted_commands: + - "delete firewall" + +parsed_config_file: "_parsed_config_1_5.cfg" + +replaced_diff: + - '+ network "1.1.1.1/32"' + - '- network "192.0.1.0/24"' + +merged_diff: + - '+ network "1.1.1.1/32"' + +deleted_diff: + - '- network "192.0.1.0/24"' diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/default_action.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/default_action.yaml new file mode 100644 index 00000000..51e16d2f --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/default_action.yaml @@ -0,0 +1,69 @@ +--- +- debug: + msg: START vyos_firewall_rules default_action integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Select the default_action values supported by VyOS {{ vyos_version }} + set_fact: + default_action_sets: >- + {{ all_sets if vyos_version is version('1.4.0', '>=', version_type='semver') + else all_sets | rejectattr('default_action', 'in', ['jump', 'return', 'continue']) | list }} + vars: + all_sets: + - name: DA-DROP + default_action: drop + - name: DA-REJECT + default_action: reject + - name: DA-ACCEPT + default_action: accept + - name: DA-JUMP + default_action: jump + default_jump_target: DA-ACCEPT + - name: DA-RETURN + default_action: return + - name: DA-CONTINUE + default_action: continue + + - name: Configure one rule-set per default_action value + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + - afi: ipv4 + rule_sets: "{{ default_action_sets }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: firewall_rules + + - name: Assert that the device accepted and reports back every default_action + assert: + that: + - "{{ default_action_sets | symmetric_difference(gathered_sets) | length == 0 }}" + vars: + gathered_sets: >- + {{ ansible_facts['network_resources']['firewall_rules'] + | selectattr('afi', 'eq', 'ipv4') | map(attribute='rule_sets') | first }} + + - name: Assert that after dicts were correctly generated + assert: + that: + - "{{ default_action_sets | symmetric_difference(after_sets) | length == 0 }}" + vars: + after_sets: >- + {{ result['after'] | selectattr('afi', 'eq', 'ipv4') | map(attribute='rule_sets') | first }} + + - name: Configure one rule-set per default_action value (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + - include_tasks: _remove_config.yaml + +- debug: + msg: END vyos_firewall_rules default_action integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/vyos_firewall_rules/vars/v1_4.yaml b/tests/integration/targets/vyos_firewall_rules/vars/v1_4.yaml index 20ee461b..0df32545 100644 --- a/tests/integration/targets/vyos_firewall_rules/vars/v1_4.yaml +++ b/tests/integration/targets/vyos_firewall_rules/vars/v1_4.yaml @@ -160,13 +160,10 @@ replaced_diff_01: source: address: 192.0.2.0 state: "{{ state_dict }}" - diff: |- - [firewall ipv4 name INBOUND rule 101] - - action \"accept\" - + action \"reject\" - - - [edit] + diff: "[firewall ipv4 name INBOUND rule 101]\n\ + - action \"accept\"\n\ + + action \"reject\"\n\n\n\ + [edit]" replaced_diff_02: config: @@ -207,15 +204,12 @@ replaced_diff_02: source: address: 192.0.2.0 state: "{{ state_dict }}" - diff: |- - [firewall ipv4 name INBOUND rule 102] - - action \"reject\" - + action \"accept\" - - protocol \"tcp\" - + protocol \"udp\" - - - [edit] + diff: "[firewall ipv4 name INBOUND rule 102]\n\ + - action \"reject\"\n\ + + action \"accept\"\n\ + - protocol \"tcp\"\n\ + + protocol \"udp\"\n\n\n\ + [edit]" replaced_diff_03: config: @@ -256,15 +250,12 @@ replaced_diff_03: source: address: 192.0.2.0 state: "{{ state_dict }}" - diff: |- - [firewall ipv4 name INBOUND rule 102] - - protocol \"udp\" - + protocol \"tcp\" - [firewall ipv6 name UPLINK rule 1] - - action \"accept\" - + action \"reject\" - - protocol \"tcp\" - + protocol \"udp\" - - - [edit] + diff: "[firewall ipv4 name INBOUND rule 102]\n\ + - protocol \"udp\"\n\ + + protocol \"tcp\"\n\ + [firewall ipv6 name UPLINK rule 1]\n\ + - action \"accept\"\n\ + + action \"reject\"\n\ + - protocol \"tcp\"\n\ + + protocol \"udp\"\n\n\n\ + [edit]" diff --git a/tests/integration/targets/vyos_ha/defaults/main.yaml b/tests/integration/targets/vyos_ha/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_ha/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_ha/tasks/cli.yaml b/tests/integration/targets/vyos_ha/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_ha/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 + register: test_cases + delegate_to: localhost + +- 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_ha/tasks/main.yaml b/tests/integration/targets/vyos_ha/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_ha/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_ha/tests/cli/_add_dummy_scripts.yaml b/tests/integration/targets/vyos_ha/tests/cli/_add_dummy_scripts.yaml new file mode 100644 index 00000000..60a44758 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_add_dummy_scripts.yaml @@ -0,0 +1,6 @@ +--- +- name: Create dummy scripts for HA tests + vyos.vyos.vyos_command: + commands: touch /var/tmp/script.sh && chmod +x /var/tmp/script.sh + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ha/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_ha/tests/cli/_parsed.cfg new file mode 100644 index 00000000..d003d106 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_parsed.cfg @@ -0,0 +1,69 @@ +set high-availability virtual-server s1 address 10.10.10.1 +set high-availability virtual-server s1 algorithm round-robin +set high-availability virtual-server s1 delay-loop 60 +set high-availability virtual-server s1 forward-method direct +set high-availability virtual-server s1 fwmark 10 +set high-availability virtual-server s1 persistence-timeout 30 +set high-availability virtual-server s1 protocol tcp +set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh +set high-availability virtual-server s1 real-server 10.10.50.1 port 443 +set high-availability virtual-server s2 address 10.10.10.2 +set high-availability virtual-server s2 port 81 +set high-availability virtual-server s2 real-server real1 connection-timeout 5 +set high-availability virtual-server s2 real-server real1 port 8081 +set high-availability virtual-server s2 real-server real2 port 8080 +set high-availability vrrp global-parameters startup-delay 31 +set high-availability vrrp global-parameters version 3 +set high-availability vrrp global-parameters garp interval 30 +set high-availability vrrp global-parameters garp master-delay 10 +set high-availability vrrp global-parameters garp master-refresh 100 +set high-availability vrrp global-parameters garp master-refresh-repeat 200 +set high-availability vrrp global-parameters garp master-repeat 5 +set high-availability vrrp group g1 address 1.1.1.1 +set high-availability vrrp group g1 address 5.5.5.5 +set high-availability vrrp group g1 address 8.8.8.8 interface eth2 +set high-availability vrrp group g1 advertise-interval 10 +set high-availability vrrp group g1 description 'Group_1' +set high-availability vrrp group g1 disable +set high-availability vrrp group g1 interface eth2 +set high-availability vrrp group g1 no-preempt +set high-availability vrrp group g1 peer-address 192.168.1.3 +set high-availability vrrp group g1 preempt-delay 15 +set high-availability vrrp group g1 priority 100 +set high-availability vrrp group g1 rfc3768-compatibility +set high-availability vrrp group g1 vrid 20 +set high-availability vrrp group g1 authentication password testpass +set high-availability vrrp group g1 authentication type plaintext-password +set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 +set high-availability vrrp group g1 excluded-address 192.168.1.8 +set high-availability vrrp group g1 garp interval 20 +set high-availability vrrp group g1 garp master-delay 5 +set high-availability vrrp group g1 garp master-refresh 50 +set high-availability vrrp group g1 garp master-refresh-repeat 100 +set high-availability vrrp group g1 garp master-repeat 3 +set high-availability vrrp group g1 track exclude-vrrp-interface +set high-availability vrrp group g1 track interface eth0 +set high-availability vrrp group g1 track interface eth1 +set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh +set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh +set high-availability vrrp group g1 transition-script master /var/tmp/script.sh +set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh +set high-availability vrrp group g2 address 2.2.2.2 +set high-availability vrrp group g2 description 'Group_2' +set high-availability vrrp group g2 interface eth1 +set high-availability vrrp group g2 vrid 11 +set high-availability vrrp group g2 health-check failure-count 5 +set high-availability vrrp group g2 health-check interval 15 +set high-availability vrrp group g2 health-check ping 192.168.1.100 +set high-availability vrrp group g2 health-check script /var/tmp/script.sh +set high-availability vrrp group g2 hello-source-address 2.2.2.2 +set high-availability vrrp sync-group sg1 health-check failure-count 3 +set high-availability vrrp sync-group sg1 health-check interval 10 +set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 +set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh +set high-availability vrrp sync-group sg1 member g1 +set high-availability vrrp sync-group sg1 member g2 +set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh diff --git a/tests/integration/targets/vyos_ha/tests/cli/_populate_config.yaml b/tests/integration/targets/vyos_ha/tests/cli/_populate_config.yaml new file mode 100644 index 00000000..95e81752 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_populate_config.yaml @@ -0,0 +1,8 @@ +--- +- ansible.builtin.include_tasks: _add_dummy_scripts.yaml + +- name: Setup initial HA configuration + vyos.vyos.vyos_config: + lines: "{{ populate_commands}}" + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ha/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_ha/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..416e5c4d --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_remove_config.yaml @@ -0,0 +1,15 @@ +--- +- name: Remove pre-existing HA config + vyos.vyos.vyos_ha: + config: + state: purged + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli + +- name: Remove dummy scripts for HA tests + vyos.vyos.vyos_command: + commands: + - rm -f /var/tmp/script.sh + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ha/tests/cli/deleted_all.yaml b/tests/integration/targets/vyos_ha/tests/cli/deleted_all.yaml new file mode 100644 index 00000000..f97e8802 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/deleted_all.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: Start vyos_ha purged / deleted_all integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Purge all the HA configuration. + register: result + vyos.vyos.vyos_ha: &id001 + config: + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ purged.commands | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "purged.after == result['after']" + + - name: Purge all the HA configuration. (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "purged.after == result['before']" + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/deleted_bulk.yaml b/tests/integration/targets/vyos_ha/tests/cli/deleted_bulk.yaml new file mode 100644 index 00000000..52a6477b --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/deleted_bulk.yaml @@ -0,0 +1,50 @@ +--- +- debug: + msg: Start vyos_ha deleted integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Delete stub objects. + register: result + vyos.vyos.vyos_ha: &id001 + config: + disable: false + vrrp: + global_parameters: + startup_delay: 32 + garp: {} + groups: + - name: g1 + sync_groups: [] + virtual_servers: [] + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + # - "{{ merged.before | symmetric_difference(result['before']) | length == 0 }}" + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_bulk['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_bulk['after'] | symmetric_difference(result['after']) | length == 0 }}" + + - name: Delete stub objects (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/deleted_vrrp_groups.yaml b/tests/integration/targets/vyos_ha/tests/cli/deleted_vrrp_groups.yaml new file mode 100644 index 00000000..b84b6905 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/deleted_vrrp_groups.yaml @@ -0,0 +1,45 @@ +--- +- debug: + msg: Start vyos_ha deleted integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Delete a vrrp group and sync_group based on its name. + register: result + vyos.vyos.vyos_ha: &id001 + config: + vrrp: + groups: + - name: "g1" + sync_groups: + - name: "sg1" + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_vrrp_groups['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "deleted_vrrp_groups['after'] == result['after']" + + - name: Delete attributes of group (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_ha/tests/cli/empty_config.yaml new file mode 100644 index 00000000..676d2c66 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: START vyos_ha 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_ha: + 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_ha: + 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_ha: + 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_ha: + 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_ha: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_ha/tests/cli/gathered.yaml b/tests/integration/targets/vyos_ha/tests/cli/gathered.yaml new file mode 100644 index 00000000..da39727a --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/gathered.yaml @@ -0,0 +1,25 @@ +--- +- debug: + msg: START vyos_ha gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Gather config from the device in structured format. + register: result + vyos.vyos.vyos_ha: + state: gathered + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - name: Assert that facts are correctly generated + assert: + that: + - result.changed == false + - "result.gathered == ansible_facts['network_resources']['ha']" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/merged.yaml b/tests/integration/targets/vyos_ha/tests/cli/merged.yaml new file mode 100644 index 00000000..cb870d01 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/merged.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_ha merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_ha: &id001 + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - assert: + that: + - result.changed == true + - result.commands | symmetric_difference(merged.commands) == [] + - "result.after == ansible_facts['network_resources']['ha']" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "merged.before == result.before" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + - "result['before'] == ansible_facts['network_resources']['ha']" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/overridden.yaml b/tests/integration/targets/vyos_ha/tests/cli/overridden.yaml new file mode 100644 index 00000000..44a85b76 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/overridden.yaml @@ -0,0 +1,35 @@ +--- +- debug: + msg: START vyos_ha overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Override the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_ha: &id001 + config: "{{ overridden.config }}" + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - name: Verify that the configuration was correctly overridden + assert: + that: + - result.changed == true + - result.after == ansible_facts['network_resources']['ha'] + + - name: Override the existing configuration with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *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_ha/tests/cli/parsed.yaml b/tests/integration/targets/vyos_ha/tests/cli/parsed.yaml new file mode 100644 index 00000000..c7134d6c --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/parsed.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_ha parsed integration tests on connection={{ ansible_connection }} + +- name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_ha: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- name: Assert that config was correctly parsed + assert: + that: + - result.changed == false + - "result.parsed == merged.before" diff --git a/tests/integration/targets/vyos_ha/tests/cli/purged.yaml b/tests/integration/targets/vyos_ha/tests/cli/purged.yaml new file mode 100644 index 00000000..ffc00d64 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/purged.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: Start vyos_ha purged / deleted_all integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Purge all the HA configuration. + register: result + vyos.vyos.vyos_ha: &id001 + config: + state: purged + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ purged.commands | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "purged.after == result['after']" + + - name: Purge all the HA configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "purged.after == result['before']" + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/rendered.yaml b/tests/integration/targets/vyos_ha/tests/cli/rendered.yaml new file mode 100644 index 00000000..efece2c6 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/rendered.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_ha rendered integration tests on connection={{ ansible_connection }} + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_ha: + config: "{{ rendered.config }}" + state: rendered + + - assert: + that: + - result.changed == false + - "result.rendered | sort == rendered.commands | sort" diff --git a/tests/integration/targets/vyos_ha/tests/cli/replaced.yaml b/tests/integration/targets/vyos_ha/tests/cli/replaced.yaml new file mode 100644 index 00000000..d35f3345 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/replaced.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_ha replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_ha: &id001 + config: "{{ merged.config }}" + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - assert: + that: + - result.changed == true + - result.commands | symmetric_difference(replaced.commands) == [] + - "result.after == ansible_facts['network_resources']['ha']" + + - name: Replace the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + - "result['before'] == ansible_facts['network_resources']['ha']" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/rtt.yaml b/tests/integration/targets/vyos_ha/tests/cli/rtt.yaml new file mode 100644 index 00000000..06e017be --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/rtt.yaml @@ -0,0 +1,39 @@ +--- +- debug: + msg: START vyos_ha rtt integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: RTT Merge the provided configuration with the existing running configuration + register: baseconfig + vyos.vyos.vyos_ha: + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - assert: + that: + - baseconfig.changed == true + - baseconfig.commands|symmetric_difference(rtt.commands) == [] + - "baseconfig.after == ansible_facts['network_resources']['ha']" + + - name: RTT Merge the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_ha: + config: "{{ rtt.config }}" + + - name: RTT Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_ha: + config: "{{ ansible_facts['network_resources']['ha'] }}" + state: overridden + + - name: RTT Assert that config was reverted + assert: + that: baseconfig.after == revert.after + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/vars/main.yaml b/tests/integration/targets/vyos_ha/vars/main.yaml new file mode 100644 index 00000000..6cf70f2e --- /dev/null +++ b/tests/integration/targets/vyos_ha/vars/main.yaml @@ -0,0 +1,787 @@ +--- +merged: + before: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: "round-robin" + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + health_check_script: "/var/tmp/script.sh" + port: 443 + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: "g1" + description: "Group_1" + interface: "eth2" + address: + - "1.1.1.1" + - "5.5.5.5" + - "8.8.8.8 interface eth2" + advertise_interval: 10 + peer_address: 192.168.1.3 + preempt_delay: 15 + priority: 100 + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + excluded_address: + - "192.168.1.7 interface eth3" + - "192.168.1.8" + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + interface: + - eth0 + - eth1 + - name: "g2" + description: "Group_2" + interface: "eth1" + address: + - "2.2.2.2" + disable: false + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + hello_source_address: "2.2.2.2" + sync_groups: + - name: "sg1" + member: + - "g1" + - "g2" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 3 + interval: 10 + ping: "192.168.2.100" + script: "/var/tmp/script.sh" + config: + disable: true + virtual_servers: + - name: s1 + address: 10.10.10.5 + algorithm: round-robin + real_server: + - address: 10.10.50.2 + port: 8443 + - name: s2 + address: 10.10.10.2 + persistence_timeout: 30 + port: 81 + protocol: tcp + - name: s3 + address: 10.10.10.3 + port: 88 + protocol: udp + vrrp: + snmp: enabled + global_parameters: + startup_delay: 30 + garp: + master_repeat: 6 + groups: + - name: "g1" + peer_address: 192.168.1.3 + priority: 100 + disable: false + no_preempt: false + vrid: 20 + - name: "g2" + address: + - "4.4.4.4" + - "6.6.6.6 interface eth3" + sync_groups: + - name: "sg1" + health_check: + failure_count: 5 + commands: + - delete high-availability vrrp group g1 disable + - delete high-availability vrrp group g1 no-preempt + - delete high-availability vrrp group g1 rfc3768-compatibility + - set high-availability disable + - set high-availability virtual-server s1 address 10.10.10.5 + - set high-availability virtual-server s1 real-server 10.10.50.2 port 8443 + - set high-availability virtual-server s2 persistence-timeout 30 + - set high-availability virtual-server s2 protocol tcp + - set high-availability virtual-server s3 address 10.10.10.3 + - set high-availability virtual-server s3 port 88 + - set high-availability virtual-server s3 protocol udp + - set high-availability vrrp global-parameters garp master-repeat 6 + - set high-availability vrrp global-parameters startup-delay 30 + - set high-availability vrrp group g2 address 2.2.2.2 + - set high-availability vrrp group g2 address 4.4.4.4 + - set high-availability vrrp group g2 address 6.6.6.6 interface eth3 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 5 + +populate_commands: + - set high-availability virtual-server s1 address 10.10.10.1 + - set high-availability virtual-server s1 algorithm round-robin + - set high-availability virtual-server s1 delay-loop 60 + - set high-availability virtual-server s1 forward-method direct + - set high-availability virtual-server s1 fwmark 10 + - set high-availability virtual-server s1 persistence-timeout 30 + - set high-availability virtual-server s1 protocol tcp + - set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh + - set high-availability virtual-server s1 real-server 10.10.50.1 port 443 + - set high-availability virtual-server s2 address 10.10.10.2 + - set high-availability virtual-server s2 port 81 + - set high-availability virtual-server s2 real-server real1 connection-timeout 5 + - set high-availability virtual-server s2 real-server real1 port 8081 + - set high-availability virtual-server s2 real-server real2 port 8080 + - set high-availability vrrp global-parameters startup-delay 31 + - set high-availability vrrp global-parameters version 3 + - set high-availability vrrp global-parameters garp interval 30 + - set high-availability vrrp global-parameters garp master-delay 10 + - set high-availability vrrp global-parameters garp master-refresh 100 + - set high-availability vrrp global-parameters garp master-refresh-repeat 200 + - set high-availability vrrp global-parameters garp master-repeat 5 + - set high-availability vrrp group g1 address 1.1.1.1 + - set high-availability vrrp group g1 address 5.5.5.5 + - set high-availability vrrp group g1 address 8.8.8.8 interface eth2 + - set high-availability vrrp group g1 advertise-interval 10 + - set high-availability vrrp group g1 description 'Group_1' + - set high-availability vrrp group g1 disable + - set high-availability vrrp group g1 interface eth2 + - set high-availability vrrp group g1 no-preempt + - set high-availability vrrp group g1 peer-address 192.168.1.3 + - set high-availability vrrp group g1 preempt-delay 15 + - set high-availability vrrp group g1 priority 100 + - set high-availability vrrp group g1 rfc3768-compatibility + - set high-availability vrrp group g1 vrid 20 + - set high-availability vrrp group g1 authentication password testpass + - set high-availability vrrp group g1 authentication type plaintext-password + - set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 + - set high-availability vrrp group g1 excluded-address 192.168.1.8 + - set high-availability vrrp group g1 garp interval 20 + - set high-availability vrrp group g1 garp master-delay 5 + - set high-availability vrrp group g1 garp master-refresh 50 + - set high-availability vrrp group g1 garp master-refresh-repeat 100 + - set high-availability vrrp group g1 garp master-repeat 3 + - set high-availability vrrp group g1 track exclude-vrrp-interface + - set high-availability vrrp group g1 track interface eth0 + - set high-availability vrrp group g1 track interface eth1 + - set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script master /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh + - set high-availability vrrp group g2 address 2.2.2.2 + - set high-availability vrrp group g2 description 'Group_2' + - set high-availability vrrp group g2 interface eth1 + - set high-availability vrrp group g2 vrid 11 + - set high-availability vrrp group g2 health-check failure-count 5 + - set high-availability vrrp group g2 health-check interval 15 + - set high-availability vrrp group g2 health-check ping 192.168.1.100 + - set high-availability vrrp group g2 health-check script /var/tmp/script.sh + - set high-availability vrrp group g2 hello-source-address 2.2.2.2 + - set high-availability vrrp sync-group sg1 health-check failure-count 3 + - set high-availability vrrp sync-group sg1 health-check interval 10 + - set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 + - set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 member g1 + - set high-availability vrrp sync-group sg1 member g2 + - set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh + +rendered: + config: + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + health_check_script: /var/tmp/script.sh + port: 443 + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + snmp: enabled + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: "g1" + description: "Group_1" + interface: "eth2" + address: + - "1.1.1.1" + - "2.2.2.2" + - "3.3.3.3 interface eth2" + advertise_interval: 10 + peer_address: 192.168.1.3 + preempt_delay: 15 + priority: 100 + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + excluded_address: + - "192.168.1.7 interface eth3" + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "test123" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + interface: + - eth0 + - name: "g2" + description: "Group_2" + interface: "eth1" + address: "2.2.2.2" + disable: false + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + hello_source_address: "2.2.2.2" + sync_groups: + - name: "sg1" + member: + - "g1" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 3 + interval: 10 + ping: "192.168.2.100" + script: "/var/tmp/script.sh" + commands: + - set high-availability virtual-server s1 address 10.10.10.1 + - set high-availability virtual-server s1 algorithm round-robin + - set high-availability virtual-server s1 delay-loop 60 + - set high-availability virtual-server s1 forward-method direct + - set high-availability virtual-server s1 fwmark 10 + - set high-availability virtual-server s1 persistence-timeout 30 + - set high-availability virtual-server s1 protocol tcp + - set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh + - set high-availability virtual-server s1 real-server 10.10.50.1 port 443 + - set high-availability virtual-server s2 address 10.10.10.2 + - set high-availability virtual-server s2 port 81 + - set high-availability virtual-server s2 real-server real1 connection-timeout 5 + - set high-availability virtual-server s2 real-server real1 port 8081 + - set high-availability virtual-server s2 real-server real2 port 8080 + - set high-availability vrrp global-parameters garp interval 30 + - set high-availability vrrp global-parameters garp master-delay 10 + - set high-availability vrrp global-parameters garp master-refresh 100 + - set high-availability vrrp global-parameters garp master-refresh-repeat 200 + - set high-availability vrrp global-parameters garp master-repeat 5 + - set high-availability vrrp global-parameters startup-delay 31 + - set high-availability vrrp global-parameters version 3 + - set high-availability vrrp group g1 address 1.1.1.1 + - set high-availability vrrp group g1 address 2.2.2.2 + - set high-availability vrrp group g1 address 3.3.3.3 interface eth2 + - set high-availability vrrp group g1 advertise-interval 10 + - set high-availability vrrp group g1 authentication password ******** + - set high-availability vrrp group g1 authentication type plaintext-password + - set high-availability vrrp group g1 description 'Group_1' + - set high-availability vrrp group g1 disable + - set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 + - set high-availability vrrp group g1 garp interval 20 + - set high-availability vrrp group g1 garp master-delay 5 + - set high-availability vrrp group g1 garp master-refresh 50 + - set high-availability vrrp group g1 garp master-refresh-repeat 100 + - set high-availability vrrp group g1 garp master-repeat 3 + - set high-availability vrrp group g1 interface eth2 + - set high-availability vrrp group g1 no-preempt + - set high-availability vrrp group g1 peer-address 192.168.1.3 + - set high-availability vrrp group g1 preempt-delay 15 + - set high-availability vrrp group g1 priority 100 + - set high-availability vrrp group g1 rfc3768-compatibility + - set high-availability vrrp group g1 track exclude-vrrp-interface + - set high-availability vrrp group g1 track interface eth0 + - set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script master /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh + - set high-availability vrrp group g1 vrid 20 + - set high-availability vrrp group g2 address 2.2.2.2 + - set high-availability vrrp group g2 description 'Group_2' + - set high-availability vrrp group g2 health-check failure-count 5 + - set high-availability vrrp group g2 health-check interval 15 + - set high-availability vrrp group g2 health-check ping 192.168.1.100 + - set high-availability vrrp group g2 health-check script /var/tmp/script.sh + - set high-availability vrrp group g2 hello-source-address 2.2.2.2 + - set high-availability vrrp group g2 interface eth1 + - set high-availability vrrp group g2 vrid 11 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 3 + - set high-availability vrrp sync-group sg1 health-check interval 10 + - set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 + - set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 member g1 + - set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh + +purged: + commands: + - delete high-availability + after: + disable: false + +deleted_vrrp_groups: + commands: + - delete high-availability vrrp group g1 + - delete high-availability vrrp sync-group sg1 + after: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + port: 443 + health_check_script: /var/tmp/script.sh + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + port: 8081 + connection_timeout: 5 + - address: real2 + port: 8080 + vrrp: + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: g2 + address: + - "2.2.2.2" + description: Group_2 + disable: false + interface: eth1 + vrid: 11 + hello_source_address: 2.2.2.2 + no_preempt: false + rfc3768_compatibility: false + health_check: + failure_count: 5 + interval: 15 + ping: 192.168.1.100 + script: /var/tmp/script.sh + +deleted_bulk: + commands: + - delete high-availability virtual-server + - delete high-availability vrrp global-parameters garp + - delete high-availability vrrp global-parameters startup-delay + - delete high-availability vrrp group g1 + - delete high-availability vrrp sync-group + after: + disable: false + vrrp: + global_parameters: + version: "3" + groups: + - name: g2 + address: + - "2.2.2.2" + description: Group_2 + disable: false + hello_source_address: 2.2.2.2 + interface: eth1 + vrid: 11 + no_preempt: false + rfc3768_compatibility: false + health_check: + failure_count: 5 + interval: 15 + ping: 192.168.1.100 + script: /var/tmp/script.sh + +overridden: + config: + disable: false + vrrp: + snmp: disabled + global_parameters: + startup_delay: 32 + version: 3 + garp: + interval: 30 + master_delay: 11 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 6 + groups: + - name: "g1" + description: "Group_1" + interface: "eth2" + address: + - "1.1.1.1" + - "3.3.3.3 interface eth3" + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + peer_address: "192.168.1.3" + advertise_interval: 10 + priority: 100 + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "test123" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + sync_groups: + - name: "sg1" + member: + - "g1" + - "g2" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 4 + interval: 10 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + virtual_servers: + - name: "s1" + address: "10.10.10.15" + algorithm: "round-robin" + delay_loop: 60 + fwmark: 12 + forward_method: "direct" + persistence_timeout: 30 + protocol: "tcp" + real_server: + - address: "10.10.10.1" + connection_timeout: 61 + port: 443 + after: + disable: false + virtual_servers: + - address: "10.10.10.15" + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 12 + name: s1 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: "10.10.10.1" + connection_timeout: 61 + port: 443 + - address: "10.10.10.2" + name: s2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + global_parameters: + garp: + interval: 30 + master_delay: 11 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 6 + startup_delay: 32 + version: "3" + groups: + - address: "1.1.1.1" + advertise_interval: 10 + authentication: + password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER + type: plaintext-password + description: Group_1 + disable: true + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + interface: eth2 + name: g1 + no_preempt: true + peer_address: "192.168.1.3" + priority: 100 + rfc3768_compatibility: true + track: + exclude_vrrp_interface: true + transition_script: + backup: /var/tmp/script.sh + fault: /var/tmp/script.sh + master: /var/tmp/script.sh + stop: /var/tmp/script.sh + vrid: 20 + - address: "2.2.2.2" + description: Group_2 + disable: false + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: /var/tmp/script.sh + hello_source_address: "2.2.2.2" + interface: eth1 + name: g2 + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + sync_groups: + - health_check: + failure_count: 4 + interval: 10 + ping: "192.168.1.100" + script: /var/tmp/script.sh + member: + - g1 + - g2 + name: sg1 + transition_script: + backup: /var/tmp/script.sh + fault: /var/tmp/script.sh + master: /var/tmp/script.sh + stop: /var/tmp/script.sh + +replaced: + commands: + - delete high-availability vrrp global-parameters garp master-repeat 5 + - delete high-availability vrrp global-parameters startup-delay 31 + - delete high-availability vrrp group g1 disable + - delete high-availability vrrp group g1 no-preempt + - delete high-availability vrrp group g1 rfc3768-compatibility + - delete high-availability vrrp group g2 address 2.2.2.2 + - delete high-availability vrrp sync-group sg1 health-check failure-count 3 + - set high-availability disable + - set high-availability virtual-server s1 address 10.10.10.5 + - set high-availability virtual-server s1 real-server 10.10.50.2 port 8443 + - set high-availability virtual-server s2 persistence-timeout 30 + - set high-availability virtual-server s2 protocol tcp + - set high-availability virtual-server s3 address 10.10.10.3 + - set high-availability virtual-server s3 port 88 + - set high-availability virtual-server s3 protocol udp + - set high-availability vrrp global-parameters garp master-repeat 6 + - set high-availability vrrp global-parameters startup-delay 30 + - set high-availability vrrp group g2 address 4.4.4.4 + - set high-availability vrrp group g2 address 6.6.6.6 interface eth3 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 5 + +rtt: + commands: + - set high-availability disable + - set high-availability virtual-server s1 address 10.10.10.5 + - set high-availability virtual-server s1 algorithm round-robin + - set high-availability virtual-server s1 real-server 10.10.50.2 port 8443 + - set high-availability virtual-server s2 address 10.10.10.2 + - set high-availability virtual-server s2 persistence-timeout 30 + - set high-availability virtual-server s2 port 81 + - set high-availability virtual-server s2 protocol tcp + - set high-availability virtual-server s3 address 10.10.10.3 + - set high-availability virtual-server s3 port 88 + - set high-availability virtual-server s3 protocol udp + - set high-availability vrrp global-parameters garp master-repeat 6 + - set high-availability vrrp global-parameters startup-delay 30 + - set high-availability vrrp group g1 peer-address 192.168.1.3 + - set high-availability vrrp group g1 priority 100 + - set high-availability vrrp group g1 vrid 20 + - set high-availability vrrp group g2 address 4.4.4.4 + - set high-availability vrrp group g2 address 6.6.6.6 interface eth3 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 5 + config: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.5 + fwmark: 10 + algorithm: least-connection + real_server: + - address: 10.10.50.2 + port: 8443 + - name: s2 + address: 10.10.10.2 + persistence_timeout: 36 + port: 443 + protocol: tcp + real_server: + - address: 10.10.50.3 + port: 8443 + - name: s3 + address: 10.10.10.3 + port: 80 + protocol: udp + real_server: + - address: 10.10.50.4 + port: 8443 + vrrp: + snmp: disabled + global_parameters: + startup_delay: 36 + garp: + master_repeat: 6 + groups: + - name: "g1" + peer_address: 192.168.1.3 + address: "1.1.1.1" + priority: 100 + disable: true + no_preempt: false + interface: eth2 + vrid: 20 + - name: "g2" + address: + - "2.2.2.2" + interface: eth1 + vrid: 11 + sync_groups: + - name: "sg1" + health_check: + failure_count: 10 + after: + disable: true + virtual_servers: + - address: "10.10.10.5" + algorithm: "round-robin" + name: "s1" + real_server: + - address: "10.10.50.2" + port: 8443 + - address: "10.10.10.2" + name: "s2" + persistence_timeout: 30 + port: 81 + protocol: "tcp" + - address: "10.10.10.3" + name: "s3" + port: 88 + protocol: "udp" + vrrp: + global_parameters: + garp: + master_repeat: 6 + startup_delay: 30 + groups: + - disable: false + name: "g1" + no_preempt: false + peer_address: "192.168.1.3" + priority: 100 + rfc3768_compatibility: false + vrid: 20 + snmp: "enabled" + sync_groups: + - health_check: + failure_count: 5 + name: "sg1" diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_interfaces/tests/cli/_parsed_config.cfg index 088354d8..cc47967c 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/_parsed_config.cfg +++ b/tests/integration/targets/vyos_interfaces/tests/cli/_parsed_config.cfg @@ -4,6 +4,7 @@ set interfaces ethernet eth1 mtu '1500' set interfaces ethernet eth1 vif 100 description 'Eth1 - VIF 100' set interfaces ethernet eth1 vif 100 mtu '1404' set interfaces ethernet eth1 vif 101 description 'Eth1 - VIF 101' +set interfaces ethernet eth1 vrf 'pink' set interfaces ethernet eth2 description 'Configured by Ansible - Interface 2 (ADMIN DOWN)' set interfaces ethernet eth2 disable set interfaces ethernet eth2 hw-id '08:00:27:d8:70:b0' diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/_populate.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/_populate.yaml index 45bd9b6a..617b2350 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/_populate.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/_populate.yaml @@ -4,12 +4,21 @@ - ansible.builtin.include_tasks: _remove_config.yaml -- name: Setup +- ansible.builtin.include_tasks: _setup_vrfs.yaml + +- name: Setup interfaces ansible.netcommon.cli_config: config: |- {% for intf in ('eth1','eth2') %} set interfaces ethernet "{{ intf }}" description 'Configured by Ansible' + set interfaces ethernet "{{ intf }}" speed 'auto' + set interfaces ethernet "{{ intf }}" duplex 'auto' set interfaces ethernet "{{ intf }}" mtu '1500' set interfaces ethernet "{{ intf }}" vif 200 set interfaces ethernet "{{ intf }}" vif 200 description 'VIF - 200' {% endfor %} + +- name: Setup - Add VRF to eth2 + ansible.netcommon.cli_config: + config: |- + set interfaces ethernet eth2 vrf 'green' diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml index 73f481bf..1f74cddf 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml @@ -10,3 +10,15 @@ delete interfaces ethernet "{{ intf }}" disable delete interfaces ethernet "{{ intf }}" vif {% endfor %} + +- name: Cleanup - Remove VRF from eth + ansible.netcommon.cli_config: + config: |- + delete interfaces ethernet eth2 vrf + delete interfaces ethernet eth1 vrf + ignore_errors: true + +- name: Cleanup - Remove VRFs + ansible.netcommon.cli_config: + config: |- + delete vrf diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/_setup_vrfs.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/_setup_vrfs.yaml new file mode 100644 index 00000000..eab29f11 --- /dev/null +++ b/tests/integration/targets/vyos_interfaces/tests/cli/_setup_vrfs.yaml @@ -0,0 +1,10 @@ +--- +- name: Setup - Add VRFs + ansible.netcommon.cli_config: + config: |- + set vrf name green description 'green' + set vrf name green table 101 + set vrf name green vni 501 + set vrf name red description 'red' + set vrf name red table 102 + set vrf name red vni 502 diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/deleted.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/deleted.yaml index 620bf53f..9f0734b4 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/deleted.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/deleted.yaml @@ -17,17 +17,17 @@ - name: Assert that the before dicts were correctly generated assert: that: - - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + - populate | symmetric_difference(result['before']) |length == 0 - name: Assert that the correct set of commands were generated assert: that: - - "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + - deleted['commands'] | symmetric_difference(result['commands']) |length == 0 - name: Assert that the after dicts were correctly generated assert: that: - - "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}" + - deleted['after'] | symmetric_difference(result['after']) |length == 0 - name: Delete attributes of given interfaces (IDEMPOTENT) register: result @@ -41,6 +41,6 @@ - name: Assert that the before dicts were correctly generated assert: that: - - "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}" + - deleted['after'] | symmetric_difference(result['before']) |length == 0 always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/gathered.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/gathered.yaml index 88e53762..46a0e166 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/gathered.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/gathered.yaml @@ -14,7 +14,7 @@ - name: Assert that gathered dicts was correctly generated assert: that: - - "{{ populate | symmetric_difference(result['gathered']) |length == 0 }}" + - populate | symmetric_difference(result['gathered']) |length == 0 always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/merged.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/merged.yaml index 5c719b39..c636fe35 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/merged.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/merged.yaml @@ -4,6 +4,8 @@ - include_tasks: _remove_config.yaml +- include_tasks: _setup_vrfs.yaml + - block: - name: Merge the provided configuration with the existing running configuration register: result @@ -12,6 +14,8 @@ - name: eth1 description: Configured by Ansible - Interface 1 mtu: 1500 + speed: auto + duplex: auto vifs: - vlan_id: 100 description: Eth1 - VIF 100 @@ -19,6 +23,7 @@ - vlan_id: 101 description: Eth1 - VIF 101 + vrf: red - name: eth2 description: Configured by Ansible - Interface 2 (ADMIN DOWN) @@ -28,17 +33,17 @@ - name: Assert that before dicts were correctly generated assert: - that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}" + that: merged['before'] | symmetric_difference(result['before']) |length == 0 - name: Assert that correct set of commands were generated assert: that: - - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + - merged['commands'] | symmetric_difference(result['commands']) |length == 0 - name: Assert that after dicts was correctly generated assert: that: - - "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}" + - merged['after'] | symmetric_difference(result['after']) |length == 0 - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) register: result @@ -52,6 +57,6 @@ - name: Assert that before dicts were correctly generated assert: that: - - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + - merged['after'] | symmetric_difference(result['before']) |length == 0 always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/overridden.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/overridden.yaml index 7e86d3e4..905470f5 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/overridden.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/overridden.yaml @@ -16,22 +16,23 @@ - name: eth2 description: Overridden by Ansible mtu: 1402 + vrf: red state: overridden - name: Assert that before dicts were correctly generated assert: that: - - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + - populate | symmetric_difference(result['before']) |length == 0 - name: Assert that correct commands were generated assert: that: - - "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + - overridden['commands'] | symmetric_difference(result['commands']) |length == 0 - name: Assert that after dicts were correctly generated assert: that: - - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + - overridden['after'] | symmetric_difference(result['after']) |length == 0 - name: Overrides all device configuration with provided configurations (IDEMPOTENT) register: result @@ -45,6 +46,6 @@ - name: Assert that before dicts were correctly generated assert: that: - - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + - overridden['after'] | symmetric_difference(result['before']) |length == 0 always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/parsed.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/parsed.yaml index 0ebfd322..329d6b50 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/parsed.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/parsed.yaml @@ -11,4 +11,4 @@ - name: Assert that config was correctly parsed assert: that: - - "{{ parsed['after'] | symmetric_difference(result['parsed']) |length == 0 }}" + - parsed['after'] | symmetric_difference(result['parsed']) |length == 0 diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/rendered.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/rendered.yaml index c03347fa..83095195 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/rendered.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/rendered.yaml @@ -11,9 +11,13 @@ config: - name: eth0 enabled: true + duplex: auto + speed: auto - name: eth1 description: Configured by Ansible - Interface 1 mtu: 1500 + duplex: auto + speed: auto enabled: true vifs: - vlan_id: 100 @@ -23,6 +27,7 @@ - vlan_id: 101 description: Eth1 - VIF 101 enabled: true + vrf: "pink" - name: eth2 description: Configured by Ansible - Interface 2 (ADMIN DOWN) mtu: 1406 @@ -32,6 +37,6 @@ - name: Assert that correct set of commands were generated assert: that: - - "{{ rendered['commands'] | symmetric_difference(result['rendered']) |length == 0 }}" + - "{{ rendered['commands'] | symmetric_difference(result['rendered']) | length == 0 }}" always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/replaced.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/replaced.yaml index 9d0a3a8b..21ddaf40 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/replaced.yaml @@ -18,6 +18,7 @@ - name: eth2 mtu: 1400 description: Replaced by Ansible + vrf: red state: replaced - name: Assert that correct set of commands were generated @@ -25,17 +26,11 @@ that: - replaced['commands'] | symmetric_difference(result['commands']) |length == 0 - - debug: - var: populate | symmetric_difference(result['before']) - - name: Assert that before dicts are correctly generated assert: that: - populate | symmetric_difference(result['before']) |length == 0 - - debug: - var: replaced['after'] | symmetric_difference(result['after']) - - name: Assert that after dict is correctly generated assert: that: diff --git a/tests/integration/targets/vyos_interfaces/tests/cli/rtt.yaml b/tests/integration/targets/vyos_interfaces/tests/cli/rtt.yaml index cfe1b0f0..667da6e9 100644 --- a/tests/integration/targets/vyos_interfaces/tests/cli/rtt.yaml +++ b/tests/integration/targets/vyos_interfaces/tests/cli/rtt.yaml @@ -4,6 +4,8 @@ - include_tasks: _remove_config.yaml +- include_tasks: _setup_vrfs.yaml + - block: - name: Apply the provided configuration (base config) register: base_config @@ -11,6 +13,8 @@ config: - name: eth0 enabled: true + duplex: auto + speed: auto - name: eth1 description: Interface - 1 @@ -28,6 +32,7 @@ description: Interface - 2 enabled: true mtu: 1409 + vrf: red state: merged - name: Gather interfaces facts @@ -53,6 +58,7 @@ description: Eth1 - VIF 101 (WILL BE REMOVED) enabled: true mtu: 1401 + vrf: green - name: eth2 description: Interface 2 (ADMIN DOWN) (WILL BE REVERTED) @@ -62,7 +68,7 @@ - name: Assert that changes were applied assert: - that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}" + that: round_trip['after'] | symmetric_difference(result['after']) |length == 0 - name: Revert back to base config using facts round trip register: revert @@ -72,6 +78,6 @@ - name: Assert that config was reverted assert: - that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}" + that: base_config['after'] | symmetric_difference(revert['after']) |length == 0 always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_interfaces/vars/main.yaml b/tests/integration/targets/vyos_interfaces/vars/main.yaml index 4e66747b..61f30049 100644 --- a/tests/integration/targets/vyos_interfaces/vars/main.yaml +++ b/tests/integration/targets/vyos_interfaces/vars/main.yaml @@ -12,9 +12,12 @@ merged: commands: - set interfaces ethernet eth1 description 'Configured by Ansible - Interface 1' - set interfaces ethernet eth1 mtu '1500' + - set interfaces ethernet eth1 duplex 'auto' + - set interfaces ethernet eth1 speed 'auto' - set interfaces ethernet eth1 vif 100 description 'Eth1 - VIF 100' - set interfaces ethernet eth1 vif 100 mtu '1404' - set interfaces ethernet eth1 vif 101 description 'Eth1 - VIF 101' + - set interfaces ethernet eth1 vrf 'red' - set interfaces ethernet eth2 description 'Configured by Ansible - Interface 2 (ADMIN DOWN)' - set interfaces ethernet eth2 mtu '1406' - set interfaces ethernet eth2 disable @@ -27,6 +30,8 @@ merged: description: Configured by Ansible - Interface 1 mtu: 1500 enabled: true + duplex: auto + speed: auto vifs: - vlan_id: 100 description: Eth1 - VIF 100 @@ -35,6 +40,7 @@ merged: - vlan_id: 101 description: Eth1 - VIF 101 enabled: true + vrf: red - name: eth2 description: Configured by Ansible - Interface 2 (ADMIN DOWN) mtu: 1406 @@ -44,6 +50,8 @@ populate: enabled: true description: Configured by Ansible mtu: 1500 + duplex: auto + speed: auto vifs: - vlan_id: 200 enabled: true @@ -52,10 +60,13 @@ populate: enabled: true description: Configured by Ansible mtu: 1500 + duplex: auto + speed: auto vifs: - vlan_id: 200 enabled: true description: VIF - 200 + vrf: "green" - name: eth0 enabled: true duplex: auto @@ -63,12 +74,17 @@ populate: replaced: commands: - delete interfaces ethernet eth1 mtu + - delete interfaces ethernet eth1 speed + - delete interfaces ethernet eth1 duplex - delete interfaces ethernet eth1 vif 200 - set interfaces ethernet eth1 description 'Replaced by Ansible' - set interfaces ethernet eth1 vif 100 description 'VIF 100 - Replaced by Ansible' - delete interfaces ethernet eth2 vif 200 + - delete interfaces ethernet eth2 speed + - delete interfaces ethernet eth2 duplex - set interfaces ethernet eth2 description 'Replaced by Ansible' - set interfaces ethernet eth2 mtu '1400' + - set interfaces ethernet eth2 vrf 'red' after: - name: eth1 description: Replaced by Ansible @@ -81,6 +97,7 @@ replaced: mtu: 1400 description: Replaced by Ansible enabled: true + vrf: red - name: eth0 enabled: true duplex: auto @@ -99,6 +116,7 @@ parsed: - vlan_id: 101 description: Eth1 - VIF 101 enabled: true + vrf: pink - name: eth2 description: Configured by Ansible - Interface 2 (ADMIN DOWN) mtu: 1406 @@ -107,10 +125,15 @@ overridden: commands: - delete interfaces ethernet eth1 description - delete interfaces ethernet eth1 mtu + - delete interfaces ethernet eth1 speed + - delete interfaces ethernet eth1 duplex - delete interfaces ethernet eth1 vif 200 - delete interfaces ethernet eth2 vif 200 + - delete interfaces ethernet eth2 speed + - delete interfaces ethernet eth2 duplex - set interfaces ethernet eth2 description 'Overridden by Ansible' - set interfaces ethernet eth2 mtu '1402' + - set interfaces ethernet eth2 vrf 'red' after: - name: eth0 enabled: true @@ -122,13 +145,19 @@ overridden: enabled: true description: Overridden by Ansible mtu: 1402 + vrf: red rendered: commands: + - set interfaces ethernet eth0 duplex 'auto' + - set interfaces ethernet eth0 speed 'auto' + - set interfaces ethernet eth1 duplex 'auto' + - set interfaces ethernet eth1 speed 'auto' - set interfaces ethernet eth1 description 'Configured by Ansible - Interface 1' - set interfaces ethernet eth1 mtu '1500' - set interfaces ethernet eth1 vif 100 description 'Eth1 - VIF 100' - set interfaces ethernet eth1 vif 100 mtu '1404' - set interfaces ethernet eth1 vif 101 description 'Eth1 - VIF 101' + - set interfaces ethernet eth1 vrf 'pink' - set interfaces ethernet eth2 description 'Configured by Ansible - Interface 2 (ADMIN DOWN)' - set interfaces ethernet eth2 mtu '1406' @@ -137,9 +166,14 @@ deleted: - delete interfaces ethernet eth1 description - delete interfaces ethernet eth1 mtu - delete interfaces ethernet eth1 vif 200 + - delete interfaces ethernet eth1 speed + - delete interfaces ethernet eth1 duplex - delete interfaces ethernet eth2 description - delete interfaces ethernet eth2 mtu - delete interfaces ethernet eth2 vif 200 + - delete interfaces ethernet eth2 speed + - delete interfaces ethernet eth2 duplex + - delete interfaces ethernet eth2 vrf after: - name: eth0 enabled: true @@ -149,6 +183,7 @@ deleted: enabled: true - name: eth2 enabled: true + round_trip: after: - name: eth0 @@ -168,7 +203,9 @@ round_trip: description: Eth1 - VIF 101 (WILL BE REMOVED) enabled: true mtu: 1401 + vrf: green - name: eth2 description: Interface 2 (ADMIN DOWN) (WILL BE REVERTED) mtu: 1406 enabled: false + vrf: red diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_l3_interfaces/tests/cli/_parsed_config.cfg index 9232a0a4..a34ac606 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/_parsed_config.cfg +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/_parsed_config.cfg @@ -1,4 +1,5 @@ set interfaces ethernet eth1 address '192.0.2.10/24' +set interfaces ethernet eth1 vif 102 ipv6 address 'autoconf' set interfaces ethernet eth1 address '2001:db8::10/32' set interfaces ethernet eth1 hw-id '08:00:27:da:67:43' set interfaces ethernet eth2 address '198.51.100.10/24' diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/_populate.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/_populate.yaml index 6f6c5066..d6ccd116 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/_populate.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/_populate.yaml @@ -7,7 +7,7 @@ set interfaces ethernet eth1 address '192.0.2.14/24' set interfaces ethernet eth2 address '192.0.2.10/24' set interfaces ethernet eth2 address '192.0.2.11/24' - set interfaces ethernet eth2 address '2001:db8::10/32' + set interfaces ethernet eth2 address '2001:db8::10/32' set interfaces ethernet eth2 address '2001:db8::12/32' ansible.netcommon.cli_config: config: "{{ lines }}" diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/_remove_config.yaml index aa5a628f..13774cd5 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/_remove_config.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/_remove_config.yaml @@ -1,7 +1,10 @@ --- - name: Remove Config vars: - lines: "delete interfaces ethernet \"{{ intf }}\" address\ndelete interfaces ethernet \"{{ intf }}\" vif\n" + lines: | + delete interfaces ethernet "{{ intf }}" address + delete interfaces ethernet "{{ intf }}" vif + delete interfaces ethernet "{{ intf }}" ipv6 loop: - eth1 - eth2 diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/deleted.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/deleted.yaml index df21b431..3af51209 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/deleted.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/deleted.yaml @@ -10,7 +10,6 @@ vyos.vyos.vyos_l3_interfaces: &id001 config: - name: eth1 - - name: eth2 state: deleted @@ -42,5 +41,42 @@ assert: that: - "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Prepare delete test for SLAAC auto-config + vyos.vyos.vyos_l3_interfaces: + config: + - name: eth1 + ipv6: + - address: auto-config + - name: eth2 + vifs: + - vlan_id: 101 + ipv6: + - address: auto-config + state: replaced + + - name: Delete SLACC auto-config of given interfaces + register: result + vyos.vyos.vyos_l3_interfaces: + config: + - name: eth1 + - name: eth2 + state: deleted + + - name: Assert that the before dicts were correctly generated (SLAAC) + assert: + that: + - "{{ deleted['before_slaac'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated (SLAAC) + assert: + that: + - "{{ deleted['commands_slaac'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated (SLAAC) + assert: + that: + - "{{ deleted['after_slaac'] | symmetric_difference(result['after']) |length == 0 }}" + always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/gathered.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/gathered.yaml index 41a53d87..0a3db05b 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/gathered.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/gathered.yaml @@ -24,5 +24,37 @@ assert: that: - result['changed'] == false + + - name: Merge the provided configuration for SLAAC tesitng + register: result + vyos.vyos.vyos_l3_interfaces: + config: + - name: eth1 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config + state: merged + + - name: Gather the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_l3_interfaces: &id002 + config: + state: gathered + + - name: Assert that gathered dicts was correctly generated for SLAAC + assert: + that: + - "{{ populate_slaac | symmetric_difference(result['gathered']) |length == 0 }}" + + - name: Gather the existing running configuration for SLAAC (IDEMPOTENT) + register: result + vyos.vyos.vyos_l3_interfaces: *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_l3_interfaces/tests/cli/merged.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/merged.yaml index 4ec4d0f2..fcb5e896 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/merged.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/merged.yaml @@ -14,6 +14,10 @@ - address: 192.0.2.10/24 ipv6: - address: 2001:db8::10/32 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config - name: eth2 ipv4: diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/overridden.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/overridden.yaml index 8334a610..240d01ac 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/overridden.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/overridden.yaml @@ -16,6 +16,14 @@ - name: eth1 ipv4: - address: 192.0.2.15/24 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config + + - name: eth2 + ipv6: + - address: auto-config state: overridden - name: Assert that before dicts were correctly generated diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/rendered.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/rendered.yaml index 199879f4..9f5de20a 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/rendered.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/rendered.yaml @@ -12,6 +12,10 @@ - name: eth1 ipv4: - address: 192.0.2.14/24 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config - name: eth2 ipv4: - address: 192.0.2.10/24 @@ -19,6 +23,12 @@ ipv6: - address: 2001:db8::10/32 - address: 2001:db8::12/32 + vifs: + - vlan_id: 101 + ipv4: + - address: 198.51.100.130/25 + ipv6: + - address: 2001:db8::20/32 state: rendered - name: Assert that correct set of commands were generated diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/cli/replaced.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/cli/replaced.yaml index 502175fc..0715172d 100644 --- a/tests/integration/targets/vyos_l3_interfaces/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/tests/cli/replaced.yaml @@ -46,5 +46,49 @@ assert: that: - "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Replace device configurations (autoconf) + register: result + vyos.vyos.vyos_l3_interfaces: &id002 + config: + - name: eth2 + ipv6: + - address: auto-config + - name: eth1 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config + state: replaced + + - name: Assert that correct set of commands were generated (autoconf) + assert: + that: + - "{{ replaced['af_commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dict is correctly generated (autoconf) + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['af_after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Replace device autoconf configurations (IDEMPOTENT) + register: result + vyos.vyos.vyos_l3_interfaces: *id002 + + - name: Assert that task was idempotent (autoconf) + assert: + that: + - result['changed'] == false + + - name: Assert that before dict is correctly generated (autoconf) + assert: + that: + - "{{ replaced['af_after'] | symmetric_difference(result['before']) |length == 0 }}" + always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml b/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml index e9781e6a..16a3d821 100644 --- a/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml +++ b/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml @@ -9,27 +9,32 @@ merged: commands: - set interfaces ethernet eth1 address '192.0.2.10/24' - set interfaces ethernet eth1 address '2001:db8::10/32' + - set interfaces ethernet eth1 vif 102 ipv6 address 'autoconf' - set interfaces ethernet eth2 address '198.51.100.10/24' - set interfaces ethernet eth2 vif 101 address '198.51.100.130/25' - set interfaces ethernet eth2 vif 101 address '2001:db8::20/32' after: - - name: eth0 - ipv4: - - address: dhcp - - name: eth1 - ipv4: - - address: 192.0.2.10/24 - ipv6: - - address: 2001:db8::10/32 - - name: eth2 - ipv4: + - ipv4: - address: 198.51.100.10/24 + name: eth2 vifs: - - vlan_id: 101 - ipv4: + - ipv4: - address: 198.51.100.130/25 ipv6: - address: 2001:db8::20/32 + vlan_id: 101 + - ipv4: + - address: dhcp + name: eth0 + - ipv4: + - address: 192.0.2.10/24 + ipv6: + - address: 2001:db8::10/32 + name: eth1 + vifs: + - ipv6: + - address: auto-config + vlan_id: 102 populate: - name: eth1 ipv4: @@ -44,6 +49,25 @@ populate: - name: eth0 ipv4: - address: dhcp + +populate_slaac: + - name: eth1 + ipv4: + - address: 192.0.2.14/24 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config + - name: eth2 + ipv4: + - address: 192.0.2.10/24 + - address: 192.0.2.11/24 + ipv6: + - address: 2001:db8::10/32 + - address: 2001:db8::12/32 + - name: eth0 + ipv4: + - address: dhcp replaced: commands: - delete interfaces ethernet eth2 address '192.0.2.10/24' @@ -53,6 +77,11 @@ replaced: - set interfaces ethernet eth2 address '2001:db8::11/32' - delete interfaces ethernet eth1 address '192.0.2.14/24' - set interfaces ethernet eth1 address '192.0.2.19/24' + af_commands: + - delete interfaces ethernet eth2 address '2001:db8::11/32' + - set interfaces ethernet eth2 ipv6 address 'autoconf' + - delete interfaces ethernet eth1 address '192.0.2.19/24' + - set interfaces ethernet eth1 vif 102 ipv6 address 'autoconf' after: - name: eth2 ipv6: @@ -63,14 +92,28 @@ replaced: - name: eth0 ipv4: - address: dhcp + af_after: + - name: eth2 + ipv6: + - address: auto-config + - name: eth1 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config + - name: eth0 + ipv4: + - address: dhcp overridden: commands: - delete interfaces ethernet eth1 address '192.0.2.14/24' - set interfaces ethernet eth1 address '192.0.2.15/24' - - delete interfaces ethernet eth2 address '192.0.2.10/24' + - set interfaces ethernet eth1 vif 102 ipv6 address 'autoconf' - delete interfaces ethernet eth2 address '192.0.2.11/24' - - delete interfaces ethernet eth2 address '2001:db8::10/32' + - delete interfaces ethernet eth2 address '192.0.2.10/24' - delete interfaces ethernet eth2 address '2001:db8::12/32' + - delete interfaces ethernet eth2 address '2001:db8::10/32' + - set interfaces ethernet eth2 ipv6 address 'autoconf' after: - name: eth0 ipv4: @@ -78,7 +121,13 @@ overridden: - name: eth1 ipv4: - address: 192.0.2.15/24 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config - name: eth2 + ipv6: + - address: auto-config parsed: after: - name: eth1 @@ -86,6 +135,10 @@ parsed: - address: 192.0.2.10/24 ipv6: - address: 2001:db8::10/32 + vifs: + - vlan_id: 102 + ipv6: + - address: auto-config - name: eth2 ipv4: - address: 198.51.100.10/24 @@ -98,10 +151,13 @@ parsed: rendered: commands: - set interfaces ethernet eth1 address '192.0.2.14/24' + - set interfaces ethernet eth1 vif 102 ipv6 address 'autoconf' - set interfaces ethernet eth2 address '192.0.2.11/24' - set interfaces ethernet eth2 address '192.0.2.10/24' - - set interfaces ethernet eth2 address '2001:db8::10/32' - set interfaces ethernet eth2 address '2001:db8::12/32' + - set interfaces ethernet eth2 address '2001:db8::10/32' + - set interfaces ethernet eth2 vif 101 address '198.51.100.130/25' + - set interfaces ethernet eth2 vif 101 address '2001:db8::20/32' deleted: commands: @@ -110,9 +166,33 @@ deleted: - delete interfaces ethernet eth2 address '192.0.2.11/24' - delete interfaces ethernet eth2 address '2001:db8::10/32' - delete interfaces ethernet eth2 address '2001:db8::12/32' + + commands_slaac: + - delete interfaces ethernet eth1 ipv6 address 'autoconf' + - delete interfaces ethernet eth2 vif 101 ipv6 address 'autoconf' after: - name: eth0 ipv4: - address: dhcp - name: eth1 - name: eth2 + before_slaac: + - name: eth0 + ipv4: + - address: dhcp + - name: eth1 + ipv6: + - address: auto-config + - name: eth2 + vifs: + - vlan_id: 101 + ipv6: + - address: auto-config + after_slaac: + - name: eth0 + ipv4: + - address: dhcp + - name: eth1 + - name: eth2 + vifs: + - vlan_id: 101 diff --git a/tests/integration/targets/vyos_logging_global/tests/cli/_get_version.yaml b/tests/integration/targets/vyos_logging_global/tests/cli/_get_version.yaml index 2588b194..d2c09490 100644 --- a/tests/integration/targets/vyos_logging_global/tests/cli/_get_version.yaml +++ b/tests/integration/targets/vyos_logging_global/tests/cli/_get_version.yaml @@ -25,4 +25,10 @@ - name: include correct vars include_vars: v1_4.yaml - when: vyos_version is version('1.4.0', '>=', version_type='semver') + when: + - vyos_version is version('1.4.0', '>=', version_type='semver') + - vyos_version is version('1.5.0', '<', version_type='semver') + +- name: include correct vars + include_vars: v1_5.yaml + when: vyos_version is version('1.5.0', '>=', version_type='semver') diff --git a/tests/integration/targets/vyos_logging_global/tests/cli/_parsed_15.cfg b/tests/integration/targets/vyos_logging_global/tests/cli/_parsed_15.cfg new file mode 100644 index 00000000..c1fcea5c --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/cli/_parsed_15.cfg @@ -0,0 +1,10 @@ +set system syslog console facility all +set system syslog console facility news level 'debug' +set system syslog local facility cron level 'debug' +set system syslog local facility local7 level 'debug' +set system syslog marker interval '111' +set system syslog preserve-fqdn +set system syslog remote 10.0.2.12 facility all level 'info' +set system syslog remote 10.0.2.15 facility all level 'all' +set system syslog remote 10.0.2.15 port '514' +set system syslog remote 10.0.2.15 protocol 'udp' diff --git a/tests/integration/targets/vyos_logging_global/tests/cli/_populate.yaml b/tests/integration/targets/vyos_logging_global/tests/cli/_populate.yaml index a17021b4..c659a577 100644 --- a/tests/integration/targets/vyos_logging_global/tests/cli/_populate.yaml +++ b/tests/integration/targets/vyos_logging_global/tests/cli/_populate.yaml @@ -5,39 +5,5 @@ - name: POPULATE Apply the provided configuration register: result vyos.vyos.vyos_logging_global: - config: - console: - facilities: - - facility: all - - facility: local7 - severity: err - - facility: news - severity: debug - files: - - path: def - archive: - file_num: 2 - facilities: - - facility: local6 - severity: emerg - - facility: local7 - severity: emerg - hosts: "{{ populate_logging_global_hosts }}" - users: - - username: vyos - facilities: - - facility: local7 - severity: debug - - facility: local6 - severity: alert - - username: paul - facilities: - - facility: local7 - severity: err - global_params: - facilities: - - facility: cron - severity: debug - - facility: local7 - severity: debug + config: "{{ populate_config }}" state: merged diff --git a/tests/integration/targets/vyos_logging_global/tests/cli/parsed.yaml b/tests/integration/targets/vyos_logging_global/tests/cli/parsed.yaml index 40a14f62..9ed8d7b2 100644 --- a/tests/integration/targets/vyos_logging_global/tests/cli/parsed.yaml +++ b/tests/integration/targets/vyos_logging_global/tests/cli/parsed.yaml @@ -2,10 +2,12 @@ - debug: msg: START vyos_logging_global parsed integration tests on connection={{ ansible_connection }} +- ansible.builtin.include_tasks: _get_version.yaml + - name: TEST - [parsed] Parse the commands for provided configuration register: result vyos.vyos.vyos_logging_global: - running_config: "{{ lookup('file', '_parsed.cfg') }}" + running_config: "{{ lookup('file', parsed_config_file) }}" state: parsed - assert: diff --git a/tests/integration/targets/vyos_logging_global/tests/cli/unsupported_15.yaml b/tests/integration/targets/vyos_logging_global/tests/cli/unsupported_15.yaml new file mode 100644 index 00000000..ee583439 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/cli/unsupported_15.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: START vyos_logging_global unsupported 1.5 keys integration tests on connection={{ ansible_connection }} + +- name: ensure facts + include_tasks: _get_version.yaml + +- block: + - name: Test warning on unsupported 1.5 keys - merged + register: result + vyos.vyos.vyos_logging_global: + config: + console: + facilities: + - facility: all + files: + - path: abc + archive: + size: 125 + users: + - username: vyos + facilities: + - facility: local7 + severity: debug + global_params: + archive: + size: 111 + facilities: + - facility: cron + severity: debug + state: merged + + - name: Assert unsupported keys warned and ignored + ansible.builtin.assert: + that: + - result.warnings | select('search', 'files') | list | length > 0 + - result.warnings | select('search', 'users') | list | length > 0 + - result.warnings | select('search', 'global_params.archive') | list | length > 0 + - "'set system syslog file' not in result.commands | join(' ')" + - "'set system syslog user' not in result.commands | join(' ')" + - "'set system syslog local facility cron level debug' in result.commands" + + always: + - include_tasks: _remove_config.yaml + + when: vyos_version is version('1.5', '>=') diff --git a/tests/integration/targets/vyos_logging_global/vars/main.yaml b/tests/integration/targets/vyos_logging_global/vars/main.yaml index 13ca654f..ed97d539 100644 --- a/tests/integration/targets/vyos_logging_global/vars/main.yaml +++ b/tests/integration/targets/vyos_logging_global/vars/main.yaml @@ -1,28 +1 @@ --- -rendered: - commands: - - set system syslog console facility all - - set system syslog file abc archive size 125 - - set system syslog host 172.16.2.15 facility all level all - - set system syslog user vyos facility local7 level debug - - set system syslog global facility cron level debug - -deleted: - commands: - - delete system syslog - after: {} -parsed: - after: - console: - facilities: - - facility: news - severity: debug - files: - - path: def - facilities: - - facility: local7 - severity: emerg - global_params: - facilities: - - facility: local7 - severity: debug diff --git a/tests/integration/targets/vyos_logging_global/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_logging_global/vars/pre-v1_4.yaml index 6af72488..b0115edf 100644 --- a/tests/integration/targets/vyos_logging_global/vars/pre-v1_4.yaml +++ b/tests/integration/targets/vyos_logging_global/vars/pre-v1_4.yaml @@ -66,3 +66,70 @@ populate_logging_global_hosts: facilities: - facility: all protocol: udp + +rendered: + commands: + - set system syslog console facility all + - set system syslog file abc archive size 125 + - set system syslog host 172.16.2.15 facility all level all + - set system syslog user vyos facility local7 level debug + - set system syslog global facility cron level debug + +deleted: + commands: + - delete system syslog + after: {} + +parsed: + after: + console: + facilities: + - facility: news + severity: debug + files: + - path: def + facilities: + - facility: local7 + severity: emerg + global_params: + facilities: + - facility: local7 + severity: debug + +populate_config: + console: + facilities: + - facility: all + - facility: local7 + severity: err + - facility: news + severity: debug + files: + - path: def + archive: + file_num: 2 + facilities: + - facility: local6 + severity: emerg + - facility: local7 + severity: emerg + hosts: "{{ populate_logging_global_hosts }}" + users: + - username: vyos + facilities: + - facility: local7 + severity: debug + - facility: local6 + severity: alert + - username: paul + facilities: + - facility: local7 + severity: err + global_params: + facilities: + - facility: cron + severity: debug + - facility: local7 + severity: debug + +parsed_config_file: "_parsed.cfg" diff --git a/tests/integration/targets/vyos_logging_global/vars/v1_4.yaml b/tests/integration/targets/vyos_logging_global/vars/v1_4.yaml index bfe5e12d..599cc42f 100644 --- a/tests/integration/targets/vyos_logging_global/vars/v1_4.yaml +++ b/tests/integration/targets/vyos_logging_global/vars/v1_4.yaml @@ -72,3 +72,70 @@ populate_logging_global_hosts: facilities: - facility: all protocol: udp + +rendered: + commands: + - set system syslog console facility all + - set system syslog file abc archive size 125 + - set system syslog host 172.16.2.15 facility all level all + - set system syslog user vyos facility local7 level debug + - set system syslog global facility cron level debug + +deleted: + commands: + - delete system syslog + after: {} + +parsed: + after: + console: + facilities: + - facility: news + severity: debug + files: + - path: def + facilities: + - facility: local7 + severity: emerg + global_params: + facilities: + - facility: local7 + severity: debug + +populate_config: + console: + facilities: + - facility: all + - facility: local7 + severity: err + - facility: news + severity: debug + files: + - path: def + archive: + file_num: 2 + facilities: + - facility: local6 + severity: emerg + - facility: local7 + severity: emerg + hosts: "{{ populate_logging_global_hosts }}" + users: + - username: vyos + facilities: + - facility: local7 + severity: debug + - facility: local6 + severity: alert + - username: paul + facilities: + - facility: local7 + severity: err + global_params: + facilities: + - facility: cron + severity: debug + - facility: local7 + severity: debug + +parsed_config_file: "_parsed.cfg" diff --git a/tests/integration/targets/vyos_logging_global/vars/v1_5.yaml b/tests/integration/targets/vyos_logging_global/vars/v1_5.yaml new file mode 100644 index 00000000..69c477d3 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/vars/v1_5.yaml @@ -0,0 +1,122 @@ +--- +merged: + before: {} + commands: + - set system syslog console facility all + - set system syslog local facility cron level debug + after: + console: + facilities: + - facility: all + global_params: + facilities: + - facility: cron + severity: debug + +overridden: + commands: + - delete system syslog local facility cron + - delete system syslog local facility local7 + - delete system syslog remote 172.16.2.12 protocol udp + - delete system syslog remote 172.16.2.15 protocol udp + - delete system syslog remote 172.16.2.12 + - delete system syslog remote 172.16.2.15 + + after: + console: + facilities: + - facility: all + - facility: local7 + severity: err + - facility: news + severity: debug + files: + - path: Myfile + +populate_logging_global_hosts: + - hostname: 172.16.2.15 + facilities: + - facility: all + severity: all + - facility: all + protocol: udp + - hostname: 172.16.2.12 + facilities: + - facility: all + protocol: udp + +rendered: + commands: + - set system syslog console facility all + - set system syslog remote 172.16.2.15 facility all level all + - set system syslog local facility cron level debug + +deleted: + commands: + - delete system syslog + after: {} + +parsed: + after: + console: + facilities: + - facility: all + - facility: news + severity: debug + global_params: + marker_interval: 111 + preserve_fqdn: true + facilities: + - facility: cron + severity: debug + - facility: local7 + severity: debug + hosts: + - hostname: 10.0.2.12 + facilities: + - facility: all + severity: info + - hostname: 10.0.2.15 + port: 514 + protocol: udp + facilities: + - facility: all + severity: all + +populate_config: + console: + facilities: + - facility: all + - facility: local7 + severity: err + - facility: news + severity: debug + files: + - path: def + archive: + file_num: 2 + facilities: + - facility: local6 + severity: emerg + - facility: local7 + severity: emerg + hosts: "{{ populate_logging_global_hosts }}" + users: + - username: vyos + facilities: + - facility: local7 + severity: debug + - facility: local6 + severity: alert + - username: paul + facilities: + - facility: local7 + severity: err + global_params: + facilities: + - facility: cron + severity: debug + - facility: local7 + severity: debug + +parsed_config_file: "_parsed_15.cfg" 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/integration/targets/vyos_route_maps/tests/cli/replaced.yaml b/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml index d7d05ec1..dce0cba9 100644 --- a/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml @@ -31,7 +31,6 @@ - assert: that: - - result.commands|length == 7 - result.changed == true - result.commands|symmetric_difference(replaced.commands) == [] - result.after|symmetric_difference(ansible_facts['network_resources']['route_maps']) == [] diff --git a/tests/integration/targets/vyos_snmp_server/tests/cli/_populate_config.yaml b/tests/integration/targets/vyos_snmp_server/tests/cli/_populate_config.yaml index f8aa82e8..5fade170 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/cli/_populate_config.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/cli/_populate_config.yaml @@ -2,6 +2,7 @@ - name: setup vyos.vyos.vyos_config: lines: + - set interface ethernet eth1 address 192.0.2.1/24 - set interface ethernet eth2 address 20.1.1.1/24 - set service snmp community switches authorization rw - set service snmp community bridges client 1.1.1.1 diff --git a/tests/integration/targets/vyos_snmp_server/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_snmp_server/tests/cli/_remove_config.yaml index 12cfb755..d30001f9 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/cli/_remove_config.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/cli/_remove_config.yaml @@ -6,3 +6,12 @@ ignore_errors: true vars: ansible_connection: ansible.netcommon.network_cli + +- name: remove interfaces address + vyos.vyos.vyos_config: + lines: + - delete interface ethernet eth1 address + - delete interface ethernet eth2 address + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/_get_version.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/_get_version.yaml new file mode 100644 index 00000000..2588b194 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/_get_version.yaml @@ -0,0 +1,28 @@ +- name: make sure to get facts + vyos.vyos.vyos_facts: + vars: + ansible_connection: ansible.netcommon.network_cli + register: vyos_facts + when: vyos_version is not defined + +- name: debug vyos_facts + debug: + var: vyos_facts + +- name: pull version from facts + set_fact: + vyos_version: "{{ vyos_facts.ansible_facts.ansible_net_version.split('-')[0].split(' ')[-1] }}" + when: vyos_version is not defined + +- name: fix '.0' versions + set_fact: + vyos_version: "{{ vyos_version }}.0" + when: vyos_version.count('.') == 1 + +- name: include correct vars + include_vars: pre-v1_4.yaml + when: vyos_version is version('1.4.0', '<', version_type='semver') + +- name: include correct vars + include_vars: v1_4.yaml + when: vyos_version is version('1.4.0', '>=', version_type='semver') diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg index b2ecd4e9..8d6638cb 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg +++ b/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg @@ -1,3 +1,4 @@ +set protocols static route 192.0.2.32/28 interface 'eth1' set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' set protocols static route 192.0.2.32/28 blackhole diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml index 52d760da..4b57d5f2 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml +++ b/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml @@ -1,11 +1,31 @@ --- - ansible.builtin.include_tasks: _remove_config.yaml -- name: Setup +- name: ensure facts + include_tasks: _get_version.yaml + +- name: Setup 1.4- + vyos.vyos.vyos_config: + lines: + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static interface-route 192.0.2.32/28 next-hop-interface eth1 + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 + vars: + ansible_connection: ansible.netcommon.network_cli + when: vyos_version is version('1.4.0', '<', version_type='semver') + +- name: Setup 1.4+ vyos.vyos.vyos_config: lines: - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static route 192.0.2.32/28 interface eth1 - set protocols static route 192.0.2.32/28 blackhole - set protocols static route 192.0.2.32/28 - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' @@ -14,3 +34,4 @@ - set protocols static route6 2001:db8:1000::/36 vars: ansible_connection: ansible.netcommon.network_cli + when: vyos_version is version('1.4.0', '>=', version_type='semver') diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml index 273f4608..0398a030 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml +++ b/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml @@ -2,6 +2,8 @@ - debug: msg: START vyos_static_routes merged integration tests on connection={{ ansible_connection }} +- include_tasks: _populate.yaml + - include_tasks: _remove_config.yaml - block: @@ -17,8 +19,8 @@ type: blackhole next_hops: - forward_router_address: 192.0.2.10 - - forward_router_address: 192.0.2.9 + - interface: eth2 - address_families: - afi: ipv6 @@ -28,7 +30,6 @@ distance: 2 next_hops: - forward_router_address: 2001:db8:2000:2::1 - - forward_router_address: 2001:db8:2000:2::2 state: merged @@ -39,7 +40,7 @@ - name: Assert that correct set of commands were generated assert: that: - - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + - "{{ merged.commands | symmetric_difference(result.commands) | length == 0 }}" - name: Assert that after dicts was correctly generated assert: diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml index da5aff47..19f6774a 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml +++ b/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml @@ -15,6 +15,7 @@ - dest: 198.0.2.48/28 next_hops: - forward_router_address: 192.0.2.18 + - interface: eth2 state: overridden - name: Assert that before dicts were correctly generated diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml index 761cff7a..9d933140 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml +++ b/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml @@ -17,7 +17,6 @@ type: blackhole next_hops: - forward_router_address: 192.0.2.10 - - forward_router_address: 192.0.2.9 - address_families: @@ -28,8 +27,8 @@ distance: 2 next_hops: - forward_router_address: 2001:db8:2000:2::1 - - forward_router_address: 2001:db8:2000:2::2 + - interface: eth2 state: rendered - name: Assert that correct set of commands were generated diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml index 26150e57..8f2a3c57 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml @@ -21,6 +21,8 @@ - forward_router_address: 192.0.2.8 - forward_router_address: 192.0.2.9 + + - interface: eth2 state: replaced - name: Assert that correct set of commands were generated diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml index 2f8d475b..c59165ef 100644 --- a/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml +++ b/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml @@ -2,6 +2,8 @@ - debug: msg: START vyos_static_routes round trip integration tests on connection={{ ansible_connection }} +- include_tasks: _get_version.yaml + - include_tasks: _remove_config.yaml - block: @@ -52,6 +54,8 @@ next_hops: - forward_router_address: 192.0.2.7 + - interface: "eth2" + - forward_router_address: 192.0.2.8 - forward_router_address: 192.0.2.9 diff --git a/tests/integration/targets/vyos_static_routes/vars/main.yaml b/tests/integration/targets/vyos_static_routes/vars/main.bak index 6ce4cea6..6ce4cea6 100644 --- a/tests/integration/targets/vyos_static_routes/vars/main.yaml +++ b/tests/integration/targets/vyos_static_routes/vars/main.bak diff --git a/tests/integration/targets/vyos_static_routes/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_static_routes/vars/pre-v1_4.yaml new file mode 100644 index 00000000..f739eb26 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/vars/pre-v1_4.yaml @@ -0,0 +1,131 @@ +--- +merged: + before: [] + commands: + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static interface-route 192.0.2.32/28 next-hop-interface 'eth2' + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - interface: eth2 + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +populate: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - interface: eth1 + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +replaced: + commands: + - delete protocols static route 192.0.2.32/28 next-hop-interface 'eth1' + - delete protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.8' + - set protocols static route 192.0.2.32/28 blackhole distance '2' + - set protocols static interface-route 192.0.2.32/28 next-hop-interface 'eth2' + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 192.0.2.7 + - forward_router_address: 192.0.2.8 + - forward_router_address: 192.0.2.9 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +overridden: + commands: + - delete protocols static route 192.0.2.32/28 + - delete protocols static route6 2001:db8:1000::/36 + - set protocols static route 198.0.2.48/28 next-hop '192.0.2.18' + - set protocols static route 198.0.2.48/28 + - set protocols static interface-route 198.0.2.48/28 next-hop-interface 'eth2' + after: + - address_families: + - afi: ipv4 + routes: + - dest: 198.0.2.48/28 + next_hops: + - interface: eth2 + - forward_router_address: 192.0.2.18 +rendered: + commands: + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 + - set protocols static interface-route6 2001:db8:1000::/36 next-hop-interface 'eth2' + +deleted_afi_all: + commands: + - delete protocols static route + - delete protocols static route6 + after: [] +round_trip: + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 192.0.2.7 + - forward_router_address: 192.0.2.8 + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 diff --git a/tests/integration/targets/vyos_static_routes/vars/v1_4.yaml b/tests/integration/targets/vyos_static_routes/vars/v1_4.yaml new file mode 100644 index 00000000..ae88711b --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/vars/v1_4.yaml @@ -0,0 +1,134 @@ +--- +merged: + before: [] + commands: + - set protocols static route 192.0.2.32/28 + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static route 192.0.2.32/28 interface 'eth2' + - set protocols static route6 2001:db8:1000::/36 + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - interface: eth2 + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +populate: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - interface: eth1 + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +replaced: + commands: + - delete protocols static route 192.0.2.32/28 interface 'eth1' + - delete protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.8' + - set protocols static route 192.0.2.32/28 blackhole distance '2' + - set protocols static route 192.0.2.32/28 interface 'eth2' + + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + distance: 2 + next_hops: + - interface: eth2 + - forward_router_address: 192.0.2.7 + - forward_router_address: 192.0.2.8 + - forward_router_address: 192.0.2.9 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +overridden: + commands: + - delete protocols static route 192.0.2.32/28 + - delete protocols static route6 2001:db8:1000::/36 + - set protocols static route 198.0.2.48/28 next-hop '192.0.2.18' + - set protocols static route 198.0.2.48/28 + - set protocols static route 198.0.2.48/28 interface 'eth2' + after: + - address_families: + - afi: ipv4 + routes: + - dest: 198.0.2.48/28 + next_hops: + - interface: eth2 + - forward_router_address: 192.0.2.18 +rendered: + commands: + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 + - set protocols static route6 2001:db8:1000::/36 interface 'eth2' + +deleted_afi_all: + commands: + - delete protocols static route + - delete protocols static route6 + after: [] +round_trip: + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + distance: 2 + next_hops: + - interface: eth2 + - forward_router_address: 192.0.2.7 + - forward_router_address: 192.0.2.8 + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 diff --git a/tests/integration/targets/vyos_system/tests/cli/basic.yaml b/tests/integration/targets/vyos_system/tests/cli/basic.yaml index 29309e22..cf08f36f 100644 --- a/tests/integration/targets/vyos_system/tests/cli/basic.yaml +++ b/tests/integration/targets/vyos_system/tests/cli/basic.yaml @@ -55,9 +55,9 @@ ignore_errors: true vyos.vyos.vyos_config: lines: - - delete system name-server name-server 192.0.2.1 - - delete system name-server name-server 192.0.2.2 - - delete system name-server name-server 192.0.2.3 + - delete system name-server 192.0.2.1 + - delete system name-server 192.0.2.2 + - delete system name-server 192.0.2.3 match: none - debug: msg="END cli/basic.yaml on connection={{ ansible_connection }}" diff --git a/tests/integration/targets/vyos_system/tests/cli/domain_search.yaml b/tests/integration/targets/vyos_system/tests/cli/domain_search.yaml new file mode 100644 index 00000000..2422d2c7 --- /dev/null +++ b/tests/integration/targets/vyos_system/tests/cli/domain_search.yaml @@ -0,0 +1,43 @@ +--- +- debug: msg="START cli/domain_search.yaml on connection={{ ansible_connection }}" + +- name: ensure facts + include_tasks: _get_version.yaml + +- name: setup + ignore_errors: true + vyos.vyos.vyos_system: + domain_search: + - nbg.bufanda.ke + state: absent + +- name: configure domain search setting + register: result + vyos.vyos.vyos_system: + domain_search: + - nbg.bufanda.ke + +- assert: + that: + - result.changed == true + - result.commands|length == 1 + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + +- name: configure domain search setting + register: result + vyos.vyos.vyos_system: + domain_search: + - nbg.bufanda.ke + +- assert: + that: + - result.changed == false + +- name: teardown + ignore_errors: true + vyos.vyos.vyos_system: + domain_search: + - nbg.bufanda.ke + state: absent + +- debug: msg="END cli/basic.yaml on connection={{ ansible_connection }}" diff --git a/tests/integration/targets/vyos_system/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_system/vars/pre-v1_4.yaml new file mode 100644 index 00000000..cb41c9c6 --- /dev/null +++ b/tests/integration/targets/vyos_system/vars/pre-v1_4.yaml @@ -0,0 +1,4 @@ +--- +merged: + commands: + - set system domain-search domain 'nbg.bufanda.ke' diff --git a/tests/integration/targets/vyos_system/vars/v1_4.yaml b/tests/integration/targets/vyos_system/vars/v1_4.yaml new file mode 100644 index 00000000..96f0b7c9 --- /dev/null +++ b/tests/integration/targets/vyos_system/vars/v1_4.yaml @@ -0,0 +1,4 @@ +--- +merged: + commands: + - set system domain-search 'nbg.bufanda.ke' diff --git a/tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/meta/main.yaml @@ -0,0 +1 @@ +--- diff --git a/tests/integration/targets/vyos_vpn_ipsec/tasks/cli.yaml b/tests/integration/targets/vyos_vpn_ipsec/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/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 + register: test_cases + delegate_to: localhost + +- 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_vpn_ipsec/tasks/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/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_vpn_ipsec/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_parsed.cfg new file mode 100644 index 00000000..85a382e7 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_parsed.cfg @@ -0,0 +1,3 @@ +set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 encryption 'aes256' +set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 hash 'sha256' +set vpn ipsec ike-group IKE-PARSE-TEST key-exchange 'ikev2' diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml new file mode 100644 index 00000000..96842807 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_populate.yaml @@ -0,0 +1,8 @@ +--- +- ansible.builtin.include_tasks: _remove_config.yaml + +- name: POPULATE Apply the provided configuration + register: result + vyos.vyos.vyos_vpn_ipsec: + config: "{{ populate_config }}" + state: merged diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..14b032e3 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/_remove_config.yaml @@ -0,0 +1,4 @@ +--- +- name: delete the provided configuration + vyos.vyos.vyos_vpn_ipsec: + state: deleted diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml new file mode 100644 index 00000000..eee2c822 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/deleted.yaml @@ -0,0 +1,69 @@ +--- +- debug: + msg: START vyos_vpn_ipsec deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: TEST - [deleted] Remove all configuration + vyos.vyos.vyos_vpn_ipsec: + state: deleted + register: result + + - name: TEST - [deleted] Assert that correct set of commands were generated + assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + - result.after == deleted.after + + - name: TEST - [deleted] Remove all configuration again (IDEMPOTENT) + vyos.vyos.vyos_vpn_ipsec: + state: deleted + register: result + + - name: TEST - [deleted] Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml + +- block: + - include_tasks: _populate.yaml + + - name: TEST - [deleted scoped] Remove only IKE-EXTRA by name + vyos.vyos.vyos_vpn_ipsec: + config: + ike_group: + - name: IKE-EXTRA + state: deleted + register: result + + - name: TEST - [deleted scoped] Assert only IKE-EXTRA was removed + assert: + that: + - result.commands == ["delete vpn ipsec ike-group IKE-EXTRA"] + - result.changed == true + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec + + - name: TEST - [deleted scoped] Assert IKE-TEST and everything else survived + vars: + redacted: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER + assert: + that: + - ansible_facts['network_resources']['vpn_ipsec']['ike_group'] == [populate_config.ike_group[0]] + - ansible_facts['network_resources']['vpn_ipsec']['esp_group'] == populate_config.esp_group + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['name'] == populate_config.profile[0].name + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['mode'] == populate_config.profile[0].authentication.mode + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['pre_shared_secret'] == redacted + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['bind_tunnel'] == populate_config.profile[0].bind_tunnel + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['esp_group'] == populate_config.profile[0].esp_group + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['ike_group'] == populate_config.profile[0].ike_group + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml new file mode 100644 index 00000000..7d0329fd --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: START vyos_vpn_ipsec 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_vpn_ipsec: + 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_vpn_ipsec: + 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_vpn_ipsec: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vpn_ipsec: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vpn_ipsec: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml new file mode 100644 index 00000000..716bc305 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/gathered.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_vpn_ipsec gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: TEST - [gathered] Gather the existing running configuration + register: result + vyos.vyos.vyos_vpn_ipsec: + state: gathered + + - name: TEST - [gathered] Assert + vars: + redacted: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER + assert: + that: + - result.changed == false + - result.gathered.esp_group == populate_config.esp_group + - result.gathered.profile[0].name == populate_config.profile[0].name + - result.gathered.profile[0].authentication.mode == populate_config.profile[0].authentication.mode + - result.gathered.profile[0].authentication.pre_shared_secret == redacted + - result.gathered.profile[0].bind_tunnel == populate_config.profile[0].bind_tunnel + - result.gathered.profile[0].esp_group == populate_config.profile[0].esp_group + - result.gathered.profile[0].ike_group == populate_config.profile[0].ike_group + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml new file mode 100644 index 00000000..6c21d107 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/merged.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_vpn_ipsec merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_vpn_ipsec: &id001 + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + - result.after == ansible_facts['network_resources']['vpn_ipsec'] + - result.after == merged.after + - result.before == {} + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vpn_ipsec: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml new file mode 100644 index 00000000..6a8eed30 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/overridden.yaml @@ -0,0 +1,36 @@ +--- +- debug: + msg: START vyos_vpn_ipsec overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Override with only ESP-TEST named -- everything else must be removed + register: result + vyos.vyos.vyos_vpn_ipsec: &id001 + config: "{{ overridden.config }}" + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + - result.after == ansible_facts['network_resources']['vpn_ipsec'] + - result.after == overridden.after + + - name: Override with the same configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vpn_ipsec: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml new file mode 100644 index 00000000..0d1197e4 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/parsed.yaml @@ -0,0 +1,14 @@ +--- +- debug: + msg: START vyos_vpn_ipsec parsed integration tests on connection={{ ansible_connection }} + +- name: TEST - [parsed] Parse the commands for provided configuration + register: result + vyos.vyos.vyos_vpn_ipsec: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- assert: + that: + - result.changed == false + - parsed['after'] == result.parsed diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml new file mode 100644 index 00000000..1babcc2b --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/rendered.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_vpn_ipsec rendered integration tests on connection={{ ansible_connection }} + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_vpn_ipsec: + config: "{{ rendered.config }}" + state: rendered + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered']) | length == 0 }}" diff --git a/tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml new file mode 100644 index 00000000..0714e8dc --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/tests/cli/replaced.yaml @@ -0,0 +1,61 @@ +--- +- debug: + msg: START vyos_vpn_ipsec replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Replace only the named esp_group -- other sections must be untouched + register: result + vyos.vyos.vyos_vpn_ipsec: &id001 + config: "{{ replaced.config }}" + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + + - name: Assert that ESP-TEST was correctly reconciled + assert: + that: + - ansible_facts['network_resources']['vpn_ipsec']['esp_group'] == replaced.after.esp_group + + - name: Assert that un-named sections were left completely untouched + vars: + redacted: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER + assert: + that: + - (ansible_facts['network_resources']['vpn_ipsec']['ike_group'] | sort(attribute='name')) == (populate_config.ike_group | sort(attribute='name')) + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['name'] == populate_config.profile[0].name + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['mode'] == populate_config.profile[0].authentication.mode + # pre_shared_secret/secret carry no_log: True in the argspec -- + # gathered facts always redact them to this literal placeholder, + # never the real value, so that's what must be asserted here. + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['authentication']['pre_shared_secret'] == redacted + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['bind_tunnel'] == populate_config.profile[0].bind_tunnel + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['esp_group'] == populate_config.profile[0].esp_group + - ansible_facts['network_resources']['vpn_ipsec']['profile'][0]['ike_group'] == populate_config.profile[0].ike_group + - ansible_facts['network_resources']['vpn_ipsec']['authentication']['psk'][0]['name'] == populate_config.authentication.psk[0].name + - ansible_facts['network_resources']['vpn_ipsec']['authentication']['psk'][0]['secret'] == redacted + - (ansible_facts['network_resources']['vpn_ipsec']['authentication']['psk'][0]['id'] | sort) == (populate_config.authentication.psk[0].id | sort) + - ansible_facts['network_resources']['vpn_ipsec']['authentication']['ppk'][0]['name'] == populate_config.authentication.ppk[0].name + - ansible_facts['network_resources']['vpn_ipsec']['authentication']['ppk'][0]['secret'] == redacted + - ansible_facts['network_resources']['vpn_ipsec']['authentication']['ppk'][0]['id'] == populate_config.authentication.ppk[0].id + + - name: Replace with the same configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vpn_ipsec: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml b/tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml new file mode 100644 index 00000000..b86a8d72 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec/vars/main.yaml @@ -0,0 +1,231 @@ +--- +merged: + config: + esp_group: + - name: ESP-TEST + compression: true + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-TEST + key_exchange: ikev2 + disable_mobike: true + dead_peer_detection: + action: restart + interval: 15 + timeout: 60 + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + dh_group: 14 + commands: + - set vpn ipsec esp-group ESP-TEST + - set vpn ipsec esp-group ESP-TEST compression + - set vpn ipsec esp-group ESP-TEST proposal 1 + - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes256 + - set vpn ipsec esp-group ESP-TEST proposal 1 hash sha256 + - set vpn ipsec ike-group IKE-TEST + - set vpn ipsec ike-group IKE-TEST key-exchange ikev2 + - set vpn ipsec ike-group IKE-TEST disable-mobike + - set vpn ipsec ike-group IKE-TEST dead-peer-detection action restart + - set vpn ipsec ike-group IKE-TEST dead-peer-detection interval 15 + - set vpn ipsec ike-group IKE-TEST dead-peer-detection timeout 60 + - set vpn ipsec ike-group IKE-TEST proposal 1 + - set vpn ipsec ike-group IKE-TEST proposal 1 encryption aes256 + - set vpn ipsec ike-group IKE-TEST proposal 1 hash sha256 + - set vpn ipsec ike-group IKE-TEST proposal 1 dh-group 14 + after: + esp_group: + - name: ESP-TEST + compression: true + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-TEST + key_exchange: ikev2 + disable_mobike: true + dead_peer_detection: + action: restart + interval: 15 + timeout: 60 + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + dh_group: 14 + +# Full pre-populated state used by replaced/overridden/deleted/gathered -- +# matches the same shape as the unit test fixture, so expected +# commands/after values here are grounded in what's already unit-tested, +# not invented fresh. +populate_config: + esp_group: + - name: ESP-TEST + compression: true + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-TEST + key_exchange: ikev2 + disable_mobike: true + dead_peer_detection: + action: restart + interval: 15 + timeout: 60 + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + dh_group: 14 + - name: IKE-EXTRA + key_exchange: ikev1 + authentication: + psk: + - name: PSK-TEST + id: + - local@example.com + - remote@example.com + secret: test-not-real-secret + ppk: + - name: PPK-TEST + id: + - ppk-id-1 + secret: test-ppk-secret + profile: + - name: testprofile + authentication: + mode: pre-shared-secret + pre_shared_secret: test-not-real-secret + bind_tunnel: + - tun0 + esp_group: ESP-TEST + ike_group: IKE-TEST + disable_uniqreqids: true + interface: + - eth0 + - eth1 + log: + level: 1 + subsystem: + - ike + - chd + options: + flexvpn: true + retransmission: + attempts: 3 + +replaced: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes128 + hash: sha256 + commands: + - delete vpn ipsec esp-group ESP-TEST compression + - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128 + - delete vpn ipsec disable-uniqreqids + - delete vpn ipsec interface eth0 + - delete vpn ipsec interface eth1 + - delete vpn ipsec log level 1 + - delete vpn ipsec log subsystem chd + - delete vpn ipsec log subsystem ike + - delete vpn ipsec options flexvpn + - delete vpn ipsec options retransmission attempts 3 + # NOTE: ike_group/profile/authentication are NOT in `after` deletions -- + # they're left untouched since replaced only reconciles items it names. + # `after` below reflects only what replaced's scope actually changes; + # the un-named sections (ike_group, profile, authentication) remain + # exactly as populate_config left them and are checked separately in + # replaced.yaml via ansible_facts rather than duplicated here. + after: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes128 + hash: sha256 + +overridden: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes128 + hash: sha256 + commands: + - delete vpn ipsec esp-group ESP-TEST compression + - set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128 + - delete vpn ipsec ike-group IKE-TEST + - delete vpn ipsec ike-group IKE-EXTRA + - delete vpn ipsec profile testprofile + - delete vpn ipsec authentication psk PSK-TEST + - delete vpn ipsec authentication ppk PPK-TEST + - delete vpn ipsec disable-uniqreqids + - delete vpn ipsec interface eth0 + - delete vpn ipsec interface eth1 + - delete vpn ipsec log level 1 + - delete vpn ipsec log subsystem chd + - delete vpn ipsec log subsystem ike + - delete vpn ipsec options flexvpn + - delete vpn ipsec options retransmission attempts 3 + after: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes128 + hash: sha256 + +deleted: + commands: + - delete vpn ipsec esp-group ESP-TEST + - delete vpn ipsec ike-group IKE-TEST + - delete vpn ipsec ike-group IKE-EXTRA + - delete vpn ipsec profile testprofile + - delete vpn ipsec authentication psk PSK-TEST + - delete vpn ipsec authentication ppk PPK-TEST + - delete vpn ipsec disable-uniqreqids + - delete vpn ipsec interface eth0 + - delete vpn ipsec interface eth1 + - delete vpn ipsec log level 1 + - delete vpn ipsec log subsystem chd + - delete vpn ipsec log subsystem ike + - delete vpn ipsec options flexvpn + - delete vpn ipsec options retransmission attempts 3 + after: {} + +rendered: + config: + esp_group: + - name: ESP-RENDER-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + commands: + - set vpn ipsec esp-group ESP-RENDER-TEST + - set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 + - set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 encryption aes256 + - set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 hash sha256 + +parsed: + after: + esp_group: + - name: ESP-PARSE-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-PARSE-TEST + key_exchange: ikev2 diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/meta/main.yaml @@ -0,0 +1 @@ +--- diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/cli.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/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 + register: test_cases + delegate_to: localhost + +- 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_vpn_ipsec_s2s/tasks/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/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_vpn_ipsec_s2s/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_parsed.cfg new file mode 100644 index 00000000..37b7fb4e --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_parsed.cfg @@ -0,0 +1 @@ +set vpn ipsec site-to-site peer PEER-PARSE-TEST ike-group 'IKE-TEST' diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml new file mode 100644 index 00000000..b196d27a --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_populate.yaml @@ -0,0 +1,37 @@ +--- +- ansible.builtin.include_tasks: _remove_config.yaml + +# Real device dependencies discovered while building this module: +# vpn ipsec site-to-site peer references esp-group/ike-group by name +# (from the vyos_vpn_ipsec profile-layer module), and vti.bind requires +# the referenced interface to already exist -- neither is created by +# this module itself. Made self-contained here rather than assuming +# vyos_vpn_ipsec's own tests already left this state behind. +- name: PREREQ Create the esp-group/ike-group referenced by the test peer + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-TEST + key_exchange: ikev2 + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + state: merged + +- name: PREREQ Create the vti0 interface referenced by vti.bind + vyos.vyos.vyos_config: + lines: + - set interfaces vti vti0 + +- name: POPULATE Apply the provided configuration + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: + config: "{{ populate_config }}" + state: merged diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..a5da6334 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/_remove_config.yaml @@ -0,0 +1,13 @@ +--- +- name: delete the provided configuration + vyos.vyos.vyos_vpn_ipsec_s2s: + state: deleted + +- name: Remove the prerequisite esp-group/ike-group + vyos.vyos.vyos_vpn_ipsec: + state: deleted + +- name: Remove the prerequisite vti0 interface + vyos.vyos.vyos_config: + lines: + - delete interfaces vti vti0 diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml new file mode 100644 index 00000000..dc870783 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/deleted.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: TEST - [deleted] Remove all configuration + vyos.vyos.vyos_vpn_ipsec_s2s: + state: deleted + register: result + + - name: TEST - [deleted] Assert that correct set of commands were generated + assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + - result.after == deleted.after + + - name: TEST - [deleted] Remove all configuration again (IDEMPOTENT) + vyos.vyos.vyos_vpn_ipsec_s2s: + state: deleted + register: result + + - name: TEST - [deleted] Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml + +- block: + - include_tasks: _populate.yaml + + - name: TEST - [deleted scoped] Remove only PEER-EXTRA by name + vyos.vyos.vyos_vpn_ipsec_s2s: + config: + peer: + - name: PEER-EXTRA + state: deleted + register: result + + - name: TEST - [deleted scoped] Assert only PEER-EXTRA was removed + assert: + that: + - result.commands == ["delete vpn ipsec site-to-site peer PEER-EXTRA"] + - result.changed == true + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec_s2s + + - name: TEST - [deleted scoped] Assert PEER-TEST survived + assert: + that: + - ansible_facts['network_resources']['vpn_ipsec_s2s']['peer'] | map(attribute='name') | list == ['PEER-TEST'] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml new file mode 100644 index 00000000..026cf22c --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s 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_vpn_ipsec_s2s: + 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_vpn_ipsec_s2s: + 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_vpn_ipsec_s2s: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vpn_ipsec_s2s: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vpn_ipsec_s2s: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml new file mode 100644 index 00000000..9b9adfd6 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: TEST - [gathered] Gather the existing running configuration + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: + state: gathered + + - name: TEST - [gathered] Assert + assert: + that: + - result.changed == false + - result.gathered.peer | map(attribute='name') | sort | list == ['PEER-EXTRA', 'PEER-TEST'] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml new file mode 100644 index 00000000..06b528fb --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/merged.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- name: PREREQ Create the esp-group/ike-group referenced by the test peer + vyos.vyos.vyos_vpn_ipsec: + config: + esp_group: + - name: ESP-TEST + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + ike_group: + - name: IKE-TEST + key_exchange: ikev2 + proposal: + - proposal_id: 1 + encryption: aes256 + hash: sha256 + state: merged + +- name: PREREQ Create the vti0 interface referenced by vti.bind + vyos.vyos.vyos_config: + lines: + - set interfaces vti vti0 + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: &id001 + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec_s2s + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + - result.before == {} + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml new file mode 100644 index 00000000..04a5a990 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/overridden.yaml @@ -0,0 +1,39 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Override with only PEER-TEST named -- PEER-EXTRA must be removed + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: &id001 + config: "{{ overridden.config }}" + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec_s2s + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + + - name: Assert that only PEER-TEST remains + assert: + that: + - ansible_facts['network_resources']['vpn_ipsec_s2s']['peer'] | map(attribute='name') | list == ['PEER-TEST'] + + - name: Override with the same configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml new file mode 100644 index 00000000..114d19d0 --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/parsed.yaml @@ -0,0 +1,14 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s parsed integration tests on connection={{ ansible_connection }} + +- name: TEST - [parsed] Parse the commands for provided configuration + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- assert: + that: + - result.changed == false + - parsed['after'] == result.parsed diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml new file mode 100644 index 00000000..dd5a8ddf --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/rendered.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s rendered integration tests on connection={{ ansible_connection }} + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: + config: "{{ rendered.config }}" + state: rendered + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered']) | length == 0 }}" diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml new file mode 100644 index 00000000..78e4158e --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/tests/cli/replaced.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_vpn_ipsec_s2s replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Replace only the named peer -- other sections must be untouched + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: &id001 + config: "{{ replaced.config }}" + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: vpn_ipsec_s2s + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + - result.changed == true + + - name: Assert that PEER-EXTRA was left completely untouched + assert: + that: + - >- + (ansible_facts['network_resources']['vpn_ipsec_s2s']['peer'] + | selectattr('name', 'equalto', 'PEER-EXTRA') | list | first) + == (populate_config.peer | selectattr('name', 'equalto', 'PEER-EXTRA') | list | first) + + - name: Replace with the same configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vpn_ipsec_s2s: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml b/tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml new file mode 100644 index 00000000..b592b73d --- /dev/null +++ b/tests/integration/targets/vyos_vpn_ipsec_s2s/vars/main.yaml @@ -0,0 +1,257 @@ +--- +populate_config: + peer: + - name: PEER-TEST + authentication: + local_id: local@example.com + mode: pre-shared-secret + remote_id: remote@example.com + childless: prefer + connection_type: initiate + default_esp_group: ESP-TEST + description: test peer for site-to-site module + force_udp_encapsulation: true + ike_group: IKE-TEST + ikev2_reauth: inherit + local_address: any + remote_address: + - 203.0.113.1 + replay_window: 32 + virtual_address: + - 0.0.0.0 + tunnel: + - tunnel_id: 1 + esp_group: ESP-TEST + priority: 10 + protocol: tcp + local: + port: 443 + prefix: + - 10.0.0.0/24 + remote: + port: 443 + prefix: + - 10.1.0.0/24 + vti: + bind: vti0 + esp_group: ESP-TEST + traffic_selector: + local: + prefix: + - 10.2.0.0/24 + remote: + prefix: + - 10.3.0.0/24 + - name: PEER-EXTRA + # Minimal, but genuinely commit-valid -- confirmed via FOUR + # separate real commit failures that VyOS requires, on every + # site-to-site peer regardless of how minimal: authentication, a + # real remote-address, a local-address (or dhcp-interface), and + # at least one of tunnel/vti (a cross-field constraint Ansible's + # argspec has no clean way to express for nested list-of-dict + # peer items -- worth documenting in the module itself, not just + # here). + ike_group: IKE-TEST + authentication: + mode: pre-shared-secret + local_id: extra-local@example.com + remote_id: extra-remote@example.com + remote_address: + - 198.51.100.1 + local_address: any + tunnel: + - tunnel_id: 1 + esp_group: ESP-TEST + +merged: + config: + peer: + - name: PEER-TEST + authentication: + local_id: local@example.com + mode: pre-shared-secret + remote_id: remote@example.com + childless: prefer + connection_type: initiate + default_esp_group: ESP-TEST + description: test peer for site-to-site module + force_udp_encapsulation: true + ike_group: IKE-TEST + ikev2_reauth: inherit + local_address: any + remote_address: + - 203.0.113.1 + replay_window: 32 + virtual_address: + - 0.0.0.0 + tunnel: + - tunnel_id: 1 + esp_group: ESP-TEST + priority: 10 + protocol: tcp + local: + port: 443 + prefix: + - 10.0.0.0/24 + remote: + port: 443 + prefix: + - 10.1.0.0/24 + vti: + bind: vti0 + esp_group: ESP-TEST + traffic_selector: + local: + prefix: + - 10.2.0.0/24 + remote: + prefix: + - 10.3.0.0/24 + commands: + - set vpn ipsec site-to-site peer PEER-TEST + - set vpn ipsec site-to-site peer PEER-TEST authentication local-id 'local@example.com' + - set vpn ipsec site-to-site peer PEER-TEST authentication remote-id 'remote@example.com' + - set vpn ipsec site-to-site peer PEER-TEST authentication mode 'pre-shared-secret' + - set vpn ipsec site-to-site peer PEER-TEST childless 'prefer' + - set vpn ipsec site-to-site peer PEER-TEST connection-type 'initiate' + - set vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST' + - set vpn ipsec site-to-site peer PEER-TEST description 'test peer for site-to-site module' + - set vpn ipsec site-to-site peer PEER-TEST ike-group 'IKE-TEST' + - set vpn ipsec site-to-site peer PEER-TEST ikev2-reauth 'inherit' + - set vpn ipsec site-to-site peer PEER-TEST local-address 'any' + - set vpn ipsec site-to-site peer PEER-TEST force-udp-encapsulation + - set vpn ipsec site-to-site peer PEER-TEST replay-window '32' + - set vpn ipsec site-to-site peer PEER-TEST remote-address '203.0.113.1' + - set vpn ipsec site-to-site peer PEER-TEST virtual-address '0.0.0.0' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 esp-group 'ESP-TEST' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 protocol 'tcp' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '10' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local port '443' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local prefix '10.0.0.0/24' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote port '443' + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote prefix '10.1.0.0/24' + - set vpn ipsec site-to-site peer PEER-TEST vti bind 'vti0' + - set vpn ipsec site-to-site peer PEER-TEST vti esp-group 'ESP-TEST' + - set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector local prefix '10.2.0.0/24' + - set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector remote prefix '10.3.0.0/24' + +replaced: + config: + peer: + - name: PEER-TEST + authentication: + local_id: local@example.com + mode: pre-shared-secret + remote_id: remote@example.com + childless: prefer + connection_type: initiate + default_esp_group: ESP-TEST + description: test peer for site-to-site module + force_udp_encapsulation: true + ike_group: IKE-TEST + ikev2_reauth: inherit + local_address: any + remote_address: + - 203.0.113.1 + replay_window: 32 + virtual_address: + - 0.0.0.0 + tunnel: + - tunnel_id: 1 + esp_group: ESP-TEST + priority: 99 + protocol: tcp + local: + port: 443 + prefix: + - 10.0.0.0/24 + remote: + port: 443 + prefix: + - 10.1.0.0/24 + vti: + bind: vti0 + esp_group: ESP-TEST + traffic_selector: + local: + prefix: + - 10.2.0.0/24 + remote: + prefix: + - 10.3.0.0/24 + # Only the priority differs from populate_config -- PEER-TEST is named + # fully/exactly otherwise, so replaced's per-item reconciliation has + # nothing else to change. PEER-EXTRA is not named at all and must + # survive untouched (checked separately in replaced.yaml). + commands: + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '99' + +overridden: + config: + peer: + - name: PEER-TEST + authentication: + local_id: local@example.com + mode: pre-shared-secret + remote_id: remote@example.com + childless: prefer + connection_type: initiate + default_esp_group: ESP-TEST + description: test peer for site-to-site module + force_udp_encapsulation: true + ike_group: IKE-TEST + ikev2_reauth: inherit + local_address: any + remote_address: + - 203.0.113.1 + replay_window: 32 + virtual_address: + - 0.0.0.0 + tunnel: + - tunnel_id: 1 + esp_group: ESP-TEST + priority: 99 + protocol: tcp + local: + port: 443 + prefix: + - 10.0.0.0/24 + remote: + port: 443 + prefix: + - 10.1.0.0/24 + vti: + bind: vti0 + esp_group: ESP-TEST + traffic_selector: + local: + prefix: + - 10.2.0.0/24 + remote: + prefix: + - 10.3.0.0/24 + commands: + - delete vpn ipsec site-to-site peer PEER-EXTRA + - set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '99' + +deleted: + commands: + - delete vpn ipsec site-to-site peer PEER-TEST + - delete vpn ipsec site-to-site peer PEER-EXTRA + after: {} + +rendered: + config: + peer: + - name: PEER-RENDER-TEST + ike_group: IKE-TEST + commands: + - set vpn ipsec site-to-site peer PEER-RENDER-TEST + - set vpn ipsec site-to-site peer PEER-RENDER-TEST ike-group 'IKE-TEST' + +parsed: + after: + peer: + - name: PEER-PARSE-TEST + ike_group: IKE-TEST diff --git a/tests/integration/targets/vyos_vrf/defaults/main.yaml b/tests/integration/targets/vyos_vrf/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_vrf/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_vrf/tasks/cli.yaml b/tests/integration/targets/vyos_vrf/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/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 + register: test_cases + delegate_to: localhost + +- 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_vrf/tasks/main.yaml b/tests/integration/targets/vyos_vrf/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/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_vrf/tests/cli/_add_rm.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_add_rm.yaml new file mode 100644 index 00000000..ddbed78e --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_add_rm.yaml @@ -0,0 +1,12 @@ +--- +- ansible.builtin.include_tasks: _remove_rm.yaml + +- name: Add Route Map + ansible.netcommon.cli_config: + config: |- + set policy route-map rm1 rule 10 action 'permit' + set policy route-map rm1 rule 10 match peer '192.0.2.32' + set policy route-map rm1 rule 10 set aggregator as '100' + set policy route-map rm1 rule 10 set as-path exclude '111' + set policy route-map rm1 rule 10 set large-community none + set policy route-map rm1 rule 10 set metric '5' diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg new file mode 100644 index 00000000..5fb7c6da --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg @@ -0,0 +1,34 @@ +set vrf bind-to-all +set vrf name vrf1 description 'Parser Test VRF 1' +set vrf name vrf1 disable +set vrf name vrf1 table 101 +set vrf name vrf1 vni 501 +set vrf name vrf2 description 'Parser Test VRF 2' +set vrf name vrf2 disable +set vrf name vrf2 table 102 +set vrf name vrf2 vni 102 +set vrf name vrf1 ip disable-forwarding +set vrf name vrf1 ip nht no-resolve-via-default +# set vrf name vrf-red ip protocol kernel route-map 'rm1' +set vrf name vrf-red ip protocol ospf route-map 'rm1' +set vrf name vrf-red ipv6 nht no-resolve-via-default +set vrf name vrf-green protocols ospf area '4' +set vrf name vrf-green protocols ospf area 4 area-type stub default-cost 20 +set vrf name vrf-green protocols ospf area 4 network 192.0.2.0/24 +set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 +set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 cost 10 +set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 +set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 cost 12 +set vrf name vrf-green protocols ospfv3 area '2' +set vrf name vrf-green protocols ospfv3 area 2 export-list export1 +set vrf name vrf-green protocols ospfv3 area 2 import-list import1 +set vrf name vrf-green protocols ospfv3 area 2 range 2001:db10::/32 +set vrf name vrf-green protocols ospfv3 area 2 range 2001:db20::/32 +set vrf name vrf-green protocols ospfv3 area 2 range 2001:db30::/32 +set vrf name vrf-green protocols static route 192.0.2.0/24 +set vrf name vrf-green protocols static route 192.0.2.0/24 blackhole distance '10' +set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.1' +set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.2' +set vrf name vrf-green protocols static route6 2001:db8::/32 +set vrf name vrf-green protocols static route6 2001:db8::/32 blackhole distance '20' +set vrf name vrf-green protocols static route6 2001:db8::/32 next-hop '2001:db8::1'" diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_populate.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_populate.yaml new file mode 100644 index 00000000..9e6253ee --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_populate.yaml @@ -0,0 +1,10 @@ +--- +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _add_rm.yaml + +- name: Setup + vyos.vyos.vyos_config: + lines: "{{ populate_config }}" + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..d909adfd --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_remove_config.yaml @@ -0,0 +1,9 @@ +--- +- name: Remove route-maps and VRF configuration + vyos.vyos.vyos_config: + lines: + - delete policy route-map rm1 + - delete vrf + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_remove_rm.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_remove_rm.yaml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_remove_rm.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml new file mode 100644 index 00000000..b6a28d0a --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: START vyos_vrf deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Delete the provided configuration + register: result + vyos.vyos.vyos_vrf: &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 with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vrf: *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_vrf/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vrf/tests/cli/empty_config.yaml new file mode 100644 index 00000000..ba1dab0d --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/empty_config.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_vrf 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_vrf: + 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_vrf: + 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_vrf: + 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_vrf: + 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_vrf: + 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_vrf/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vrf/tests/cli/gathered.yaml new file mode 100644 index 00000000..7ea7b286 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/gathered.yaml @@ -0,0 +1,22 @@ +--- +- debug: + msg: START vyos_vrf gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Gather config from the device in structured format. + register: result + vyos.vyos.vyos_vrf: + state: gathered + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - result.changed == false + - result.gathered|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml b/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml new file mode 100644 index 00000000..0a93ae02 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml @@ -0,0 +1,54 @@ +--- +- debug: + msg: START vyos_vrf merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + bind_to_all: true + instances: + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + - name: "vrf-pink" + description: "pink-vrf" + table_id: 111 + vni: 1111 + protocols: + bgp: + as_number: 65005 + neighbor: + - address: 192.0.5.1 + remote_as: 65012 + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - result.changed == true + - result.commands|symmetric_difference(merged.commands) == [] + - result.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - 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_vrf: *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_vrf/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vrf/tests/cli/overridden.yaml new file mode 100644 index 00000000..e21ae185 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/overridden.yaml @@ -0,0 +1,49 @@ +--- +- debug: + msg: START vyos_vrf overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Override the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: true + table_id: 103 + vni: 1002 + - name: "vrf-pink" + description: "pink-vrf" + table_id: 111 + vni: 1111 + protocols: + bgp: + as_number: 65005 + neighbor: + - address: 192.0.2.1 + remote_as: 65002 + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - result.changed == true + - result.commands|symmetric_difference(overridden.commands) == [] + - result.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Override the existing configuration with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vrf: *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_vrf/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vrf/tests/cli/parsed.yaml new file mode 100644 index 00000000..7f6a4b3d --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/parsed.yaml @@ -0,0 +1,16 @@ +--- +- debug: + msg: START vyos_vrf parsed integration tests on connection={{ ansible_connection }} + +- name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_vrf: + 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_vrf/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vrf/tests/cli/rendered.yaml new file mode 100644 index 00000000..b8b26d12 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/rendered.yaml @@ -0,0 +1,133 @@ +--- +- debug: + msg: START vyos_route_maps rendered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_vrf: + config: + bind_to_all: true + instances: + - name: vrf-green + description: green-vrf + disabled: true + table_id: 105 + vni: 1000 + protocols: + bgp: + as_number: 65000 + neighbor: + - address: 192.0.2.1 + remote_as: 65002 + - address: "1.1.1.3" + passive: true + remote_as: 400 + ospf: + log_adjacency_changes: detail + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + mpls_te: + router_address: 192.0.11.12 + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: 192.0.11.12 + poll_interval: 10 + priority: 2 + redistribute: + - route_type: bgp + metric: 10 + metric_type: 2 + parameters: + router_id: 192.0.1.1 + rfc1583_compatibility: true + abr_type: cisco + areas: + - area_id: "2" + area_type: + normal: true + authentication: plaintext-password + shortcut: enable + - area_id: "3" + area_type: + nssa: + set: true + - area_id: "4" + area_type: + stub: + default_cost: 20 + network: + - address: 192.0.2.0/24 + range: + - address: 192.0.3.0/24 + cost: 10 + - address: 192.0.4.0/24 + cost: 12 + ospfv3: + areas: + - area_id: "2" + export_list: export1 + import_list: import1 + range: + - address: 2001:db10::/32 + - address: 2001:db20::/32 + - address: 2001:db30::/32 + - area_id: "3" + range: + - address: 2001:db40::/32 + parameters: + router_id: 192.0.2.10 + redistribute: + - route_type: bgp + static: + - address_families: + - afi: ipv4 + routes: + - dest: "192.0.2.0/24" + blackhole_config: + distance: 10 + next_hops: + - forward_router_address: "203.0.113.1" + - forward_router_address: "203.0.113.2" + - afi: ipv6 + routes: + - dest: "2001:db8::/32" + blackhole_config: + distance: 20 + next_hops: + - forward_router_address: "2001:db8::1" + - name: vrf-amber + description: amber-vrf + disable: false + table_id: 111 + vni: 1001 + address_family: + - afi: ipv4 + disable_forwarding: true + route_maps: + # - rm_name: rm1 + # protocol: kernel + - rm_name: rm1 + protocol: ospf + - afi: ipv6 + nht_no_resolve_via_default: false + 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_vrf/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vrf/tests/cli/replaced.yaml new file mode 100644 index 00000000..68d3e450 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/replaced.yaml @@ -0,0 +1,56 @@ +--- +- debug: + msg: START vyos_vrf replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: true + table_id: 100 + vni: 1000 + - name: "vrf-red" + description: "Vermillion_VRF" + disable: false + table_id: 101 + vni: 1011 + address_family: + - afi: "ipv6" + nht_no_resolve_via_default: false + - name: "vrf-pink" + table_id: 111 + protocols: + ospf: + default_information: + originate: + always: true + metric: 20 + metric_type: 1 + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - result.changed == true + - result.commands|symmetric_difference(replaced.commands) == [] + - result.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Replace the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vrf: *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_vrf/tests/cli/rtt.yaml b/tests/integration/targets/vyos_vrf/tests/cli/rtt.yaml new file mode 100644 index 00000000..e8e9ea2c --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/rtt.yaml @@ -0,0 +1,69 @@ +--- +- debug: + msg: START vyos_vrf merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: baseconfig + vyos.vyos.vyos_vrf: + config: + bind_to_all: true + instances: + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + - name: "vrf-pink" + description: "pink-vrf" + table_id: 111 + vni: 1111 + protocols: + bgp: + as_number: 65005 + neighbor: + - address: 192.0.5.1 + remote_as: 65012 + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - baseconfig.changed == true + - baseconfig.commands|symmetric_difference(merged.commands) == [] + - baseconfig.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Merge the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_vrf: + config: + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: true + table_id: 100 + vni: 1000 + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + address_family: + - afi: "ipv4" + disable_forwarding: true + state: merged + + - name: Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_vrf: + config: "{{ ansible_facts['network_resources']['vrf'] }}" + 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_vrf/vars/main.yaml b/tests/integration/targets/vyos_vrf/vars/main.yaml new file mode 100644 index 00000000..9620cf32 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/vars/main.yaml @@ -0,0 +1,278 @@ +--- +merged: + before: + bind_to_all: true + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: false + table_id: 100 + vni: 1000 + - name: "vrf-red" + description: "red-vrf" + disable: true + table_id: 101 + vni: 1001 + address_family: + - afi: "ipv4" + disable_forwarding: true + route_maps: + - rm_name: "rm1" + protocol: "rip" + - afi: "ipv6" + nht_no_resolve_via_default: true + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + protocols: + ospf: + areas: + - area_id: "4" + area_type: + stub: + default_cost: 20 + network: + - address: 192.0.2.0/24 + range: + - address: 192.0.3.0/24 + cost: 10 + - address: 192.0.4.0/24 + cost: 12 + ospfv3: + areas: + - area_id: "2" + export_list: export1 + import_list: import1 + range: + - address: 2001:db10::/32 + - address: 2001:db20::/32 + - address: 2001:db30::/32 + static: + - address_families: + - afi: ipv4 + routes: + - dest: "192.0.2.0/24" + blackhole_config: + distance: 10 + next_hops: + - forward_router_address: "203.0.113.1" + - forward_router_address: "203.0.113.2" + - afi: ipv6 + routes: + - dest: "2001:db8::/32" + blackhole_config: + distance: 20 + next_hops: + - forward_router_address: "2001:db8::1" + commands: + - set vrf name vrf-green table 110 + - set vrf name vrf-green vni 1010 + - set vrf name vrf-green description green-vrf + - set vrf name vrf-pink protocols bgp system-as 65005 + - set vrf name vrf-pink protocols bgp neighbor 192.0.5.1 remote-as 65012 + after: + bind_to_all: true + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: false + table_id: 100 + vni: 1000 + - name: "vrf-red" + description: "red-vrf" + disable: true + table_id: 101 + vni: 1001 + address_family: + - afi: "ipv4" + disable_forwarding: true + route_maps: + - rm_name: "rm1" + protocol: "rip" + - afi: "ipv6" + nht_no_resolve_via_default: true + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + +replaced: + commands: + - delete vrf bind-to-all + - set vrf name vrf-blue disable + - set vrf name vrf-red vni 1011 + - set vrf name vrf-red description Vermillion_VRF + - delete vrf name vrf-red disable + - delete vrf name vrf-red ipv6 nht no-resolve-via-default + - delete vrf name vrf-pink vni 1111 + - delete vrf name vrf-pink description pink-vrf + - delete vrf name vrf-pink protocols ospf log-adjacency-changes 'detail' + - delete vrf name vrf-pink protocols ospf auto-cost + - delete vrf name vrf-pink protocols ospf max-metric + - delete vrf name vrf-pink protocols ospf mpls-te + - delete vrf name vrf-pink protocols ospf area + - delete vrf name vrf-pink protocols ospf parameters + - delete vrf name vrf-pink protocols ospf neighbor + - delete vrf name vrf-pink protocols ospf redistribute + - set vrf name vrf-pink protocols ospf default-information originate metric 20 + - set vrf name vrf-pink protocols ospf default-information originate metric-type 1 + after: [] + +overridden: + commands: + - delete vrf name vrf-blue + - commit + - delete vrf name vrf-pink + - commit + - set vrf name vrf-blue table 103 + - set vrf name vrf-blue vni 1002 + - set vrf name vrf-blue description blue-vrf + - set vrf name vrf-blue disable + - set vrf name vrf-pink table 111 + - set vrf name vrf-pink vni 1111 + - set vrf name vrf-pink description pink-vrf + - set vrf name vrf-pink protocols bgp system-as 65005 + - set vrf name vrf-pink protocols bgp neighbor 192.0.2.1 remote-as 65002 + - delete vrf bind-to-all + after: [] + +deleted: + commands: + - delete vrf + after: [] + +rendered: + commands: + - set vrf bind-to-all + - set vrf name vrf-green table 105 + - set vrf name vrf-green vni 1000 + - set vrf name vrf-green description green-vrf + - set vrf name vrf-green disable + - set vrf name vrf-green protocols bgp system-as 65000 + - set vrf name vrf-green protocols bgp neighbor 192.0.2.1 remote-as 65002 + - set vrf name vrf-green protocols bgp neighbor 1.1.1.3 passive + - set vrf name vrf-green protocols bgp neighbor 1.1.1.3 remote-as 400 + - set vrf name vrf-green protocols ospf log-adjacency-changes 'detail' + - set vrf name vrf-green protocols ospf max-metric router-lsa administrative + - set vrf name vrf-green protocols ospf max-metric router-lsa on-shutdown 10 + - set vrf name vrf-green protocols ospf max-metric router-lsa on-startup 10 + - set vrf name vrf-green protocols ospf default-information originate always + - set vrf name vrf-green protocols ospf default-information originate metric 10 + - set vrf name vrf-green protocols ospf default-information originate metric-type 2 + - set vrf name vrf-green protocols ospf mpls-te router-address '192.0.11.12' + - set vrf name vrf-green protocols ospf auto-cost reference-bandwidth '2' + - set vrf name vrf-green protocols ospf neighbor 192.0.11.12 + - set vrf name vrf-green protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set vrf name vrf-green protocols ospf neighbor 192.0.11.12 priority 2 + - set vrf name vrf-green protocols ospf redistribute bgp + - set vrf name vrf-green protocols ospf redistribute bgp metric 10 + - set vrf name vrf-green protocols ospf redistribute bgp metric-type 2 + - set vrf name vrf-green protocols ospf parameters router-id '192.0.1.1' + - set vrf name vrf-green protocols ospf parameters rfc1583-compatibility + - set vrf name vrf-green protocols ospf parameters abr-type 'cisco' + - set vrf name vrf-green protocols ospf area '2' + - set vrf name vrf-green protocols ospf area 2 area-type normal + - set vrf name vrf-green protocols ospf area 2 authentication plaintext-password + - set vrf name vrf-green protocols ospf area 2 shortcut enable + - set vrf name vrf-green protocols ospf area '3' + - set vrf name vrf-green protocols ospf area 3 area-type nssa + - set vrf name vrf-green protocols ospf area '4' + - set vrf name vrf-green protocols ospf area 4 area-type stub default-cost 20 + - set vrf name vrf-green protocols ospf area 4 network 192.0.2.0/24 + - set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 + - set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 + - set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set vrf name vrf-green protocols ospfv3 area '2' + - set vrf name vrf-green protocols ospfv3 area 2 export-list export1 + - set vrf name vrf-green protocols ospfv3 area 2 import-list import1 + - set vrf name vrf-green protocols ospfv3 area 2 range 2001:db10::/32 + - set vrf name vrf-green protocols ospfv3 area 2 range 2001:db20::/32 + - set vrf name vrf-green protocols ospfv3 area 2 range 2001:db30::/32 + - set vrf name vrf-green protocols ospfv3 area '3' + - set vrf name vrf-green protocols ospfv3 area 3 range 2001:db40::/32 + - set vrf name vrf-green protocols ospfv3 parameters router-id '192.0.2.10' + - set vrf name vrf-green protocols ospfv3 redistribute bgp + - set vrf name vrf-green protocols static route 192.0.2.0/24 + - set vrf name vrf-green protocols static route 192.0.2.0/24 blackhole distance '10' + - set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.1' + - set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.2' + - set vrf name vrf-green protocols static route6 2001:db8::/32 + - set vrf name vrf-green protocols static route6 2001:db8::/32 blackhole distance '20' + - set vrf name vrf-green protocols static route6 2001:db8::/32 next-hop '2001:db8::1' + - set vrf name vrf-amber table 111 + - set vrf name vrf-amber vni 1001 + - set vrf name vrf-amber description amber-vrf + # - set vrf name vrf-amber ip protocol kernel route-map rm1 + - set vrf name vrf-amber ip protocol ospf route-map rm1 + - set vrf name vrf-amber ip disable-forwarding + +populate_config: + - set vrf bind-to-all + - set vrf name vrf-blue description 'blue-vrf' + - set vrf name vrf-blue table '100' + - set vrf name vrf-blue vni '1000' + - set vrf name vrf-red description 'red-vrf' + - set vrf name vrf-red disable + - set vrf name vrf-red ip disable-forwarding + # - set vrf name vrf-red ip protocol kernel route-map 'rm1' + - set vrf name vrf-red ip protocol rip route-map 'rm1' + - set vrf name vrf-red ipv6 nht no-resolve-via-default + - set vrf name vrf-red table '101' + - set vrf name vrf-red vni '1001' + - set vrf name vrf-pink table 111 + - set vrf name vrf-pink vni 1111 + - set vrf name vrf-pink description pink-vrf + - set vrf name vrf-pink protocols bgp system-as 65000 + - set vrf name vrf-pink protocols bgp neighbor 192.0.2.1 remote-as 65002 + - set vrf name vrf-pink protocols bgp neighbor 1.1.1.3 passive + - set vrf name vrf-pink protocols bgp neighbor 1.1.1.3 remote-as 400 + - set vrf name vrf-pink protocols ospf log-adjacency-changes 'detail' + - set vrf name vrf-pink protocols ospf max-metric router-lsa administrative + - set vrf name vrf-pink protocols ospf max-metric router-lsa on-shutdown 10 + - set vrf name vrf-pink protocols ospf max-metric router-lsa on-startup 10 + - set vrf name vrf-pink protocols ospf default-information originate always + - set vrf name vrf-pink protocols ospf default-information originate metric 10 + - set vrf name vrf-pink protocols ospf default-information originate metric-type 2 + - set vrf name vrf-pink protocols ospf mpls-te router-address '192.0.11.12' + - set vrf name vrf-pink protocols ospf auto-cost reference-bandwidth '2' + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 priority 2 + - set vrf name vrf-pink protocols ospf redistribute bgp + - set vrf name vrf-pink protocols ospf redistribute bgp metric 10 + - set vrf name vrf-pink protocols ospf redistribute bgp metric-type 2 + - set vrf name vrf-pink protocols ospf parameters router-id '192.0.1.1' + - set vrf name vrf-pink protocols ospf parameters rfc1583-compatibility + - set vrf name vrf-pink protocols ospf parameters abr-type 'cisco' + - set vrf name vrf-pink protocols ospf area '2' + - set vrf name vrf-pink protocols ospf area 2 area-type normal + - set vrf name vrf-pink protocols ospf area 2 authentication plaintext-password + - set vrf name vrf-pink protocols ospf area 2 shortcut enable + - set vrf name vrf-pink protocols ospf area '3' + - set vrf name vrf-pink protocols ospf area 3 area-type nssa + - set vrf name vrf-pink protocols ospf area '4' + - set vrf name vrf-pink protocols ospf area 4 area-type stub default-cost 20 + - set vrf name vrf-pink protocols ospf area 4 network 192.0.2.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.3.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.4.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set vrf name vrf-pink protocols ospfv3 area '2' + - set vrf name vrf-pink protocols ospfv3 area 2 export-list export1 + - set vrf name vrf-pink protocols ospfv3 area 2 import-list import1 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db10::/32 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db20::/32 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db30::/32 + - set vrf name vrf-pink protocols ospfv3 area '3' + - set vrf name vrf-pink protocols ospfv3 area 3 range 2001:db40::/32 + - set vrf name vrf-pink protocols ospfv3 parameters router-id '192.0.2.10' + - set vrf name vrf-pink protocols ospfv3 redistribute bgp + - set vrf name vrf-pink protocols static route 192.0.2.0/24 + - set vrf name vrf-pink protocols static route 192.0.2.0/24 blackhole distance '10' + - set vrf name vrf-pink protocols static route 192.0.2.0/24 next-hop '203.0.113.1' + - set vrf name vrf-pink protocols static route 192.0.2.0/24 next-hop '203.0.113.2' + - set vrf name vrf-pink protocols static route6 2001:db8::/32 + - set vrf name vrf-pink protocols static route6 2001:db8::/32 blackhole distance '20' + - set vrf name vrf-pink protocols static route6 2001:db8::/32 next-hop '2001:db8::1' diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index c835eef8..ee3d2aac 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -1 +1,39 @@ plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index c835eef8..ee3d2aac 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -1 +1,39 @@ plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index c835eef8..ee3d2aac 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -1 +1,39 @@ plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index c835eef8..ee3d2aac 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -1 +1,39 @@ plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt index c835eef8..ee3d2aac 100644 --- a/tests/sanity/ignore-2.19.txt +++ b/tests/sanity/ignore-2.19.txt @@ -1 +1,39 @@ plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.20.txt b/tests/sanity/ignore-2.20.txt new file mode 100644 index 00000000..a96f7e91 --- /dev/null +++ b/tests/sanity/ignore-2.20.txt @@ -0,0 +1,39 @@ +plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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 +plugins/action/ospfv2.py action-plugin-docs +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.21.txt b/tests/sanity/ignore-2.21.txt new file mode 100644 index 00000000..ee3d2aac --- /dev/null +++ b/tests/sanity/ignore-2.21.txt @@ -0,0 +1,39 @@ +plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/sanity/ignore-2.22.txt b/tests/sanity/ignore-2.22.txt new file mode 100644 index 00000000..ee3d2aac --- /dev/null +++ b/tests/sanity/ignore-2.22.txt @@ -0,0 +1,39 @@ +plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/action/banner.py action-plugin-docs +plugins/action/bgp_address_family.py action-plugin-docs +plugins/action/bgp_global.py action-plugin-docs +plugins/action/command.py action-plugin-docs +plugins/action/config.py action-plugin-docs +plugins/action/facts.py action-plugin-docs +plugins/action/firewall_global.py action-plugin-docs +plugins/action/firewall_interfaces.py action-plugin-docs +plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs +plugins/action/hostname.py action-plugin-docs +plugins/action/interface.py action-plugin-docs +plugins/action/interfaces.py action-plugin-docs +plugins/action/l3_interface.py action-plugin-docs +plugins/action/l3_interfaces.py action-plugin-docs +plugins/action/lag_interfaces.py action-plugin-docs +plugins/action/linkagg.py action-plugin-docs +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/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 +plugins/action/ospfv3.py action-plugin-docs +plugins/action/ping.py action-plugin-docs +plugins/action/prefix_lists.py action-plugin-docs +plugins/action/route_maps.py action-plugin-docs +plugins/action/snmp_server.py action-plugin-docs +plugins/action/static_route.py action-plugin-docs +plugins/action/static_routes.py action-plugin-docs +plugins/action/system.py action-plugin-docs +plugins/action/user.py action-plugin-docs +plugins/action/vlan.py action-plugin-docs +plugins/action/vrf.py action-plugin-docs +plugins/action/file.py action-plugin-docs diff --git a/tests/unit/mock/loader.py b/tests/unit/mock/loader.py index 67b84c62..2828f38e 100644 --- a/tests/unit/mock/loader.py +++ b/tests/unit/mock/loader.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type import os diff --git a/tests/unit/mock/path.py b/tests/unit/mock/path.py index 1f1b7f0a..3962f24f 100644 --- a/tests/unit/mock/path.py +++ b/tests/unit/mock/path.py @@ -1,10 +1,8 @@ from __future__ import absolute_import, division, print_function - __metaclass__ = type from ansible.utils.path import unfrackpath from ansible_collections.vyos.vyos.tests.unit.compat.mock import MagicMock - mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) diff --git a/tests/unit/mock/procenv.py b/tests/unit/mock/procenv.py index d7f3dc95..2ae0c173 100644 --- a/tests/unit/mock/procenv.py +++ b/tests/unit/mock/procenv.py @@ -19,7 +19,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type import json @@ -29,7 +28,6 @@ from contextlib import contextmanager from io import BytesIO, StringIO from ansible.module_utils._text import to_bytes -from ansible.module_utils.six import PY3 from ansible_collections.vyos.vyos.tests.unit.compat import unittest @@ -42,11 +40,11 @@ def swap_stdin_and_argv(stdin_data="", argv_data=tuple()): real_stdin = sys.stdin real_argv = sys.argv - if PY3: - fake_stream = StringIO(stdin_data) - fake_stream.buffer = BytesIO(to_bytes(stdin_data)) - else: - fake_stream = BytesIO(to_bytes(stdin_data)) + # if PY3: + fake_stream = StringIO(stdin_data) + fake_stream.buffer = BytesIO(to_bytes(stdin_data)) + # else: + # fake_stream = BytesIO(to_bytes(stdin_data)) try: sys.stdin = fake_stream @@ -65,10 +63,10 @@ def swap_stdout(): """ old_stdout = sys.stdout - if PY3: - fake_stream = StringIO() - else: - fake_stream = BytesIO() + # if PY3: + fake_stream = StringIO() + # else: + # fake_stream = BytesIO() try: sys.stdout = fake_stream diff --git a/tests/unit/mock/vault_helper.py b/tests/unit/mock/vault_helper.py index 6006ba96..4c6c890c 100644 --- a/tests/unit/mock/vault_helper.py +++ b/tests/unit/mock/vault_helper.py @@ -14,7 +14,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from ansible.module_utils._text import to_bytes diff --git a/tests/unit/mock/yaml_helper.py b/tests/unit/mock/yaml_helper.py index 2e857592..65d4f7bc 100644 --- a/tests/unit/mock/yaml_helper.py +++ b/tests/unit/mock/yaml_helper.py @@ -1,12 +1,10 @@ from __future__ import absolute_import, division, print_function - __metaclass__ = type import io import yaml -from ansible.module_utils.six import PY3 from ansible.parsing.yaml.dumper import AnsibleDumper from ansible.parsing.yaml.loader import AnsibleLoader @@ -22,17 +20,11 @@ class YamlTestUtils(object): def _dump_stream(self, obj, stream, dumper=None): """Dump to a py2-unicode or py3-string stream.""" - if PY3: - return yaml.dump(obj, stream, Dumper=dumper) - else: - return yaml.dump(obj, stream, Dumper=dumper, encoding=None) + return yaml.dump(obj, stream, Dumper=dumper) def _dump_string(self, obj, dumper=None): """Dump to a py2-unicode or py3-string""" - if PY3: - return yaml.dump(obj, Dumper=dumper) - else: - return yaml.dump(obj, Dumper=dumper, encoding=None) + return yaml.dump(obj, Dumper=dumper) def _dump_load_cycle(self, obj): # Each pass though a dump or load revs the 'generation' @@ -89,22 +81,8 @@ class YamlTestUtils(object): stream_obj_from_stream = io.StringIO() stream_obj_from_string = io.StringIO() - if PY3: - yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) - yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) - else: - yaml.dump( - obj_from_stream, - stream_obj_from_stream, - Dumper=AnsibleDumper, - encoding=None, - ) - yaml.dump( - obj_from_stream, - stream_obj_from_string, - Dumper=AnsibleDumper, - encoding=None, - ) + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() @@ -112,20 +90,8 @@ class YamlTestUtils(object): stream_obj_from_stream.seek(0) stream_obj_from_string.seek(0) - if PY3: - yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) - yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) - else: - yaml_string_obj_from_stream = yaml.dump( - obj_from_stream, - Dumper=AnsibleDumper, - encoding=None, - ) - yaml_string_obj_from_string = yaml.dump( - obj_from_string, - Dumper=AnsibleDumper, - encoding=None, - ) + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) assert yaml_string == yaml_string_obj_from_stream assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string diff --git a/tests/unit/modules/conftest.py b/tests/unit/modules/conftest.py index 41465c30..b084d2f0 100644 --- a/tests/unit/modules/conftest.py +++ b/tests/unit/modules/conftest.py @@ -2,7 +2,6 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function - __metaclass__ = type import json @@ -11,12 +10,11 @@ import pytest from ansible.module_utils._text import to_bytes from ansible.module_utils.common._collections_compat import MutableMapping -from ansible.module_utils.six import string_types @pytest.fixture def patch_ansible_module(request, mocker): - if isinstance(request.param, string_types): + if isinstance(request.param, str): args = request.param elif isinstance(request.param, MutableMapping): if "ANSIBLE_MODULE_ARGS" not in request.param: diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config.cfg index f54a03dc..464f132f 100644 --- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config.cfg +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config.cfg @@ -2,6 +2,8 @@ set firewall group address-group RND-HOSTS address 192.0.2.1 set firewall group address-group RND-HOSTS address 192.0.2.3 set firewall group address-group RND-HOSTS address 192.0.2.5 set firewall group address-group RND-HOSTS description 'This group has the Management hosts address lists' +set firewall group address-group DELETE-HOSTS address 1.2.3.4 +set firewall group address-group DELETE-HOSTS description 'The (single) last address from this group will be deleted in the tests' set firewall group ipv6-address-group LOCAL-v6 address ::1 set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1 set firewall group ipv6-address-group LOCAL-v6 description 'This group has the hosts address lists of this machine' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg index 0a1247dd..f58f166c 100644 --- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg @@ -1,7 +1,10 @@ +set firewall group address-group A-EMPTY set firewall group address-group RND-HOSTS address 192.0.2.1 set firewall group address-group RND-HOSTS address 192.0.2.3 set firewall group address-group RND-HOSTS address 192.0.2.5 set firewall group address-group RND-HOSTS description 'This group has the Management hosts address lists' +set firewall group address-group DELETE-HOSTS address 1.2.3.4 +set firewall group address-group DELETE-HOSTS description 'The (single) last address from this group will be deleted in the tests' set firewall group ipv6-address-group LOCAL-v6 address ::1 set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1 set firewall group ipv6-address-group LOCAL-v6 description 'This group has the hosts address lists of this machine' @@ -16,3 +19,12 @@ set firewall global-options state-policy related action 'accept' set firewall global-options state-policy related log-level 'alert' set firewall global-options ipv6-src-route 'enable' set firewall global-options send-redirects 'enable' +set firewall zone ZONE-TEST interface 'eth0.1234' +set firewall zone ZONE-TEST description 'zone-test test description' +set firewall group address-group ZONE-TEST address '1.2.3.4' +set firewall zone ZONE-IZF description 'zone for izf and sources delete path testing' +set firewall zone ZONE-IZF intra-zone-filtering action 'drop' +set firewall zone ZONE-IZF from zone-src firewall name 'existing-ruleset' +set firewall name TESTRULESET-V4 default-action drop +set firewall ipv6-name TESTRULESET-V6-LEGACY default-action drop +set firewall ipv6 name TESTRULESET-V6-1_4PLUS default-action drop diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg new file mode 100644 index 00000000..23dd9777 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg @@ -0,0 +1,11 @@ +# Intentionally minimal: represents a VyOS 1.5.0 device with one existing +# zone already configured using the 1.5.0 'member interface' syntax. Used +# to test parse_zone's unwrap logic in isolation. TestVyosFirewallRulesModule15's +# other tests (e.g. test_01_merged) create a differently-named zone +# (ZONE-15), so under 'merged' state this existing zone is never touched +# and doesn't interfere. +set firewall zone ZONE-15-EXISTING member interface eth2 +set firewall zone ZONE-15-EXISTING description 'existing 1.5.0 zone for facts parsing test' +set firewall name TESTRULESET-V4 default-action drop +set firewall ipv6-name TESTRULESET-V6-LEGACY default-action drop +set firewall ipv6 name TESTRULESET-V6-1_4PLUS default-action drop diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg index 3ad6ec97..9665f86b 100644 --- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg @@ -1,3 +1,5 @@ +set firewall flowtable test interface 'eth1' +set firewall flowtable test offload 'software' set firewall name V4-INGRESS default-action 'accept' set firewall ipv6-name V6-INGRESS default-action 'accept' set firewall name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set' @@ -18,4 +20,6 @@ set firewall name MULTIPLE-RULE default-action 'drop' set firewall name MULTIPLE-RULE rule 1 action 'accept' set firewall name MULTIPLE-RULE rule 1 protocol 'all' set firewall name MULTIPLE-RULE rule 2 action 'drop' -set firewall name MULTIPLE-RULE rule 2 protocol 'all'
\ No newline at end of file +set firewall name MULTIPLE-RULE rule 2 protocol 'all' +set firewall name V4-OFFLOAD rule 1 action 'offload' +set firewall name V4-OFFLOAD rule 1 offload-target 'test' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg index 7f63dd78..6f69211a 100644 --- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg @@ -1,3 +1,5 @@ +set firewall flowtable test interface 'eth1' +set firewall flowtable test offload 'software' set firewall ipv4 name V4-INGRESS default-action 'accept' set firewall ipv6 name V6-INGRESS default-action 'accept' set firewall ipv4 name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set' @@ -36,4 +38,7 @@ set firewall ipv4 name MULTIPLE-RULE default-action 'drop' set firewall ipv4 name MULTIPLE-RULE rule 1 action 'accept' set firewall ipv4 name MULTIPLE-RULE rule 1 protocol 'all' set firewall ipv4 name MULTIPLE-RULE rule 2 action 'drop' -set firewall ipv4 name MULTIPLE-RULE rule 2 protocol 'all'
\ No newline at end of file +set firewall ipv4 name MULTIPLE-RULE rule 2 protocol 'all' +set firewall ipv4 name V4-OFFLOAD rule 105 +set firewall ipv4 name V4-OFFLOAD rule 105 action 'offload' +set firewall ipv4 name V4-OFFLOAD rule 105 offload-target 'test' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_ha_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_ha_config.cfg new file mode 100644 index 00000000..ee69c65d --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_ha_config.cfg @@ -0,0 +1,64 @@ +set high-availability disable +set high-availability virtual-server s1 address 10.10.10.1 +set high-availability virtual-server s1 algorithm round-robin +set high-availability virtual-server s1 delay-loop 60 +set high-availability virtual-server s1 forward-method direct +set high-availability virtual-server s1 fwmark 10 +set high-availability virtual-server s1 persistence-timeout 30 +set high-availability virtual-server s1 protocol tcp +set high-availability virtual-server s1 real-server 10.10.50.1 health-check script '/var/tmp/script.sh' +set high-availability virtual-server s1 real-server 10.10.50.1 port 443 +set high-availability virtual-server s2 address 10.10.10.2 +set high-availability virtual-server s2 port 81 +set high-availability virtual-server s2 real-server real1 connection-timeout 5 +set high-availability virtual-server s2 real-server real1 port 8081 +set high-availability virtual-server s2 real-server real2 port 8080 +set high-availability vrrp global-parameters garp interval 30 +set high-availability vrrp global-parameters garp master-delay 10 +set high-availability vrrp global-parameters garp master-refresh 100 +set high-availability vrrp global-parameters garp master-refresh-repeat 200 +set high-availability vrrp global-parameters garp master-repeat 5 +set high-availability vrrp global-parameters startup-delay 30 +set high-availability vrrp global-parameters version 3 +set high-availability vrrp group g1 address '1.1.1.1' +set high-availability vrrp group g1 address '3.3.3.3' +set high-availability vrrp group g1 address '5.5.5.5' interface 'eth2' +set high-availability vrrp group g1 advertise-interval 10 +set high-availability vrrp group g1 authentication password 'testpass' +set high-availability vrrp group g1 authentication type 'plaintext-password' +set high-availability vrrp group g1 description 'Group_1' +set high-availability vrrp group g1 disable +set high-availability vrrp group g1 excluded-address '192.168.1.7' interface 'eth3' +set high-availability vrrp group g1 excluded-address '192.168.1.8' +set high-availability vrrp group g1 garp interval 20 +set high-availability vrrp group g1 garp master-delay 5 +set high-availability vrrp group g1 garp master-refresh 50 +set high-availability vrrp group g1 garp master-refresh-repeat 100 +set high-availability vrrp group g1 garp master-repeat 3 +set high-availability vrrp group g1 health-check failure-count 3 +set high-availability vrrp group g1 health-check interval 10 +set high-availability vrrp group g1 health-check ping '192.168.1.5' +set high-availability vrrp group g1 health-check script 'script.sh' +set high-availability vrrp group g1 hello-source-address '192.168.1.2' +set high-availability vrrp group g1 interface 'eth2' +set high-availability vrrp group g1 no-preempt +set high-availability vrrp group g1 peer-address '192.168.1.3' +set high-availability vrrp group g1 priority 100 +set high-availability vrrp group g1 rfc3768-compatibility +set high-availability vrrp group g1 track exclude-vrrp-interface +set high-availability vrrp group g1 track interface 'eth1' +set high-availability vrrp group g1 transition-script backup '/var/tmp/script.sh' +set high-availability vrrp group g1 transition-script fault '/var/tmp/script.sh' +set high-availability vrrp group g1 transition-script master '/var/tmp/script.sh' +set high-availability vrrp group g1 transition-script stop '/var/tmp/script.sh' +set high-availability vrrp group g1 vrid 20 +set high-availability vrrp snmp +set high-availability vrrp sync-group sg1 health-check failure-count 3 +set high-availability vrrp sync-group sg1 health-check interval 10 +set high-availability vrrp sync-group sg1 health-check ping '192.168.1.1' +set high-availability vrrp sync-group sg1 health-check script '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 member 'g1' +set high-availability vrrp sync-group sg1 transition-script backup '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 transition-script fault '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 transition-script master '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 transition-script stop '/var/tmp/script.sh' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config_vrf.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config_vrf.cfg new file mode 100644 index 00000000..39afd31b --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config_vrf.cfg @@ -0,0 +1 @@ +set interfaces ethernet eth2 vrf 'green' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_l3_interfaces_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_l3_interfaces_config.cfg new file mode 100644 index 00000000..8f9609cd --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_l3_interfaces_config.cfg @@ -0,0 +1,8 @@ +set interfaces ethernet eth0 address 'dhcp' +set interfaces ethernet eth1 address '192.0.2.14/24' +set interfaces ethernet eth2 address '192.0.2.10/24' +set interfaces ethernet eth2 address '2001:db8::10/32' +set interfaces ethernet eth3 address '198.51.100.10/24' +set interfaces ethernet eth3 vif 101 address '198.51.100.130/25' +set interfaces ethernet eth3 vif 102 address '2001:db8:4000::3/34' +set interfaces loopback 'lo' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_lldp_interfaces_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_lldp_interfaces_config.cfg new file mode 100644 index 00000000..767daca8 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_lldp_interfaces_config.cfg @@ -0,0 +1,5 @@ +set service lldp interface eth1 location elin '0000000911' +set service lldp interface eth2 location coordinate-based altitude '2200' +set service lldp interface eth2 location coordinate-based datum 'WGS84' +set service lldp interface eth2 location coordinate-based latitude '33.524449N' +set service lldp interface eth2 location coordinate-based longitude '222.267255W' 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/fixtures/vyos_route_maps_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_route_maps_config_v14.cfg new file mode 100644 index 00000000..1e84e5eb --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_route_maps_config_v14.cfg @@ -0,0 +1,18 @@ +set policy route-map test3 rule 1 action 'permit' +set policy route-map test3 rule 1 match interface 'eth2' +set policy route-map test3 rule 1 match ipv6 nexthop 'fdda:5cc1:23:4::1f' +set policy route-map test3 rule 1 match metric '1' +set policy route-map test3 rule 1 match peer '1.1.1.2' +set policy route-map test3 rule 1 match rpki 'invalid' +set policy route-map test3 rule 1 set bgp-extcommunity-rt '22:11' +set policy route-map test3 rule 1 set community replace 'internet' +set policy route-map test3 rule 1 set ipv6-next-hop global 'fdda:5cc1:23:4::1f' +set policy route-map test3 rule 1 set ip-next-hop '10.20.10.20' +set policy route-map test3 rule 1 set local-preference '4' +set policy route-map test3 rule 1 set metric '5' +set policy route-map test3 rule 1 set metric-type 'type-1' +set policy route-map test3 rule 1 set origin 'egp' +set policy route-map test3 rule 1 set originator-id '10.0.2.3' +set policy route-map test3 rule 1 set src '10.0.2.15' +set policy route-map test3 rule 1 set tag '5' +set policy route-map test3 rule 1 set weight '4' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vlan_show_interfaces.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vlan_show_interfaces.cfg new file mode 100644 index 00000000..b877a4b6 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vlan_show_interfaces.cfg @@ -0,0 +1,10 @@ +Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down +Interface IP Address S/L Description +--------- ---------- --- ----------- +eth0 10.0.2.15/24 u/u +eth0.100 - u/u vlan-100 +eth1 - u/u +eth1.200 192.0.2.1/24 u/u vlan-200 +eth2 - u/u +lo 127.0.0.1/8 u/u + ::1/128 diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vlan_show_interfaces_empty.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vlan_show_interfaces_empty.cfg new file mode 100644 index 00000000..06ae56a0 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vlan_show_interfaces_empty.cfg @@ -0,0 +1,7 @@ +Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down +Interface IP Address S/L Description +--------- ---------- --- ----------- +eth0 10.0.2.15/24 u/u +eth1 - u/u +eth2 - u/u +lo 127.0.0.1/8 u/u diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg new file mode 100644 index 00000000..d0d0e19e --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_config.cfg @@ -0,0 +1,30 @@ +set vpn ipsec authentication ppk PPK-TEST id 'ppk-id-1' +set vpn ipsec authentication ppk PPK-TEST secret 'test-ppk-secret' +set vpn ipsec authentication psk PSK-TEST id 'local@example.com' +set vpn ipsec authentication psk PSK-TEST id 'remote@example.com' +set vpn ipsec authentication psk PSK-TEST secret 'test-not-real-secret' +set vpn ipsec disable-uniqreqids +set vpn ipsec esp-group ESP-TEST compression +set vpn ipsec esp-group ESP-TEST proposal 1 encryption 'aes256' +set vpn ipsec esp-group ESP-TEST proposal 1 hash 'sha256' +set vpn ipsec ike-group IKE-TEST key-exchange 'ikev2' +set vpn ipsec ike-group IKE-TEST dead-peer-detection action 'restart' +set vpn ipsec ike-group IKE-TEST dead-peer-detection interval '15' +set vpn ipsec ike-group IKE-TEST dead-peer-detection timeout '60' +set vpn ipsec ike-group IKE-TEST disable-mobike +set vpn ipsec ike-group IKE-TEST proposal 1 dh-group '14' +set vpn ipsec ike-group IKE-TEST proposal 1 encryption 'aes256' +set vpn ipsec ike-group IKE-TEST proposal 1 hash 'sha256' +set vpn ipsec ike-group IKE-EXTRA key-exchange 'ikev1' +set vpn ipsec interface 'eth0' +set vpn ipsec interface 'eth1' +set vpn ipsec log level '1' +set vpn ipsec log subsystem 'ike' +set vpn ipsec log subsystem 'chd' +set vpn ipsec options flexvpn +set vpn ipsec options retransmission attempts '3' +set vpn ipsec profile testprofile authentication mode 'pre-shared-secret' +set vpn ipsec profile testprofile authentication pre-shared-secret 'test-not-real-secret' +set vpn ipsec profile testprofile bind tunnel 'tun0' +set vpn ipsec profile testprofile esp-group 'ESP-TEST' +set vpn ipsec profile testprofile ike-group 'IKE-TEST' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg new file mode 100644 index 00000000..0d674b8f --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vpn_ipsec_s2s_config.cfg @@ -0,0 +1,26 @@ +set vpn ipsec site-to-site peer PEER-TEST authentication local-id 'local@example.com' +set vpn ipsec site-to-site peer PEER-TEST authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer PEER-TEST authentication remote-id 'remote@example.com' +set vpn ipsec site-to-site peer PEER-TEST childless 'prefer' +set vpn ipsec site-to-site peer PEER-TEST connection-type 'initiate' +set vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST' +set vpn ipsec site-to-site peer PEER-TEST description 'test peer for site-to-site module' +set vpn ipsec site-to-site peer PEER-TEST force-udp-encapsulation +set vpn ipsec site-to-site peer PEER-TEST ike-group 'IKE-TEST' +set vpn ipsec site-to-site peer PEER-TEST ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer PEER-TEST local-address 'any' +set vpn ipsec site-to-site peer PEER-TEST remote-address '203.0.113.1' +set vpn ipsec site-to-site peer PEER-TEST replay-window '32' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 esp-group 'ESP-TEST' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local port '443' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 local prefix '10.0.0.0/24' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '10' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 protocol 'tcp' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote port '443' +set vpn ipsec site-to-site peer PEER-TEST tunnel 1 remote prefix '10.1.0.0/24' +set vpn ipsec site-to-site peer PEER-TEST virtual-address '0.0.0.0' +set vpn ipsec site-to-site peer PEER-TEST vti bind 'vti0' +set vpn ipsec site-to-site peer PEER-TEST vti esp-group 'ESP-TEST' +set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector local prefix '10.2.0.0/24' +set vpn ipsec site-to-site peer PEER-TEST vti traffic-selector remote prefix '10.3.0.0/24' +set vpn ipsec site-to-site peer PEER-EXTRA ike-group 'IKE-TEST' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg new file mode 100644 index 00000000..094b7cf3 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg @@ -0,0 +1,15 @@ +set vrf bind-to-all +set vrf name vrf-blue description 'blue-vrf' +set vrf name vrf-blue table '100' +set vrf name vrf-blue vni '1000' +set vrf name vrf-red description 'red-vrf' +set vrf name vrf-red disable +set vrf name vrf-red ip disable-forwarding +set vrf name vrf-red ip protocol rip route-map 'rm1' +set vrf name vrf-red ipv6 nht no-resolve-via-default +set vrf name vrf-red table '101' +set vrf name vrf-red vni '1001' +set vrf name vrf-red protocols bgp system-as 65000 +set vrf name vrf-red protocols bgp neighbor 192.0.2.1 remote-as 65002 +set vrf name vrf-red protocols bgp neighbor 1.1.1.3 passive +set vrf name vrf-red protocols bgp neighbor 1.1.1.3 remote-as 400 diff --git a/tests/unit/modules/network/vyos/test_rm_templates_perf.py b/tests/unit/modules/network/vyos/test_rm_templates_perf.py new file mode 100644 index 00000000..d2060f46 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_rm_templates_perf.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""Performance budget tests for rm_templates parsers. + +These tests guard against re-introducing catastrophic regex backtracking +in the rm_template parsers (T8609). Pre-fix, parse() over realistic +device-output input could take 50+ seconds because of `(group)*` +quantifiers on groups containing `\\S+`. Post-fix, the same input +parses in single-digit milliseconds. + +A 1-second budget is comfortably above post-fix runtime and well below +the pre-regression cliff, so the test fails sharply if the bug returns. +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import time + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family_14 import ( + Bgp_address_familyTemplate14, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global_14 import ( + Bgp_globalTemplate14, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import ( + Route_mapsTemplate14, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.snmp_server import ( + Snmp_serverTemplate, +) + + +PARSE_BUDGET_SECONDS = 1.0 + + +def _time_parse(parser_class, lines): + """Time a single ``parse()`` call against ``lines``; returns elapsed seconds.""" + parser = parser_class(lines=lines) + t0 = time.perf_counter() + parser.parse() + return time.perf_counter() - t0 + + +def test_route_maps_14_parse_budget(): + """Realistic route-map config: parse() must finish under 1s. + + Pre-T8609: ~50s. Post-fix: <50ms. Inputs use 20+ char names + because the backtracking is exponential in the first \\S+ run + after the prefix. + """ + lines = [ + "set policy route-map ADVERTISE-ANYCAST-v6 rule 10 action 'permit'", + "set policy route-map ADVERTISE-ANYCAST-v6 rule 10 match ipv6 address prefix-list 'ANYCAST-AGGREGATE-v6'", + "set policy route-map DEFAULT-ORIGINATE-SENTINEL-v6 rule 10 action 'permit'", + "set policy route-map DEFAULT-ORIGINATE-SENTINEL-v6 rule 10 match ipv6 address prefix-list 'AS64496-SENTINEL-v6'", + "set policy route-map DEFAULT-ORIGINATE-SENTINEL-v6 rule 10 set local-preference '120'", + "set policy route-map IXP-PEER-INGRESS-v4 rule 10 action 'permit'", + "set policy route-map IXP-PEER-INGRESS-v4 rule 10 match ip address prefix-list 'IXP-INBOUND-v4'", + "set policy route-map IXP-PEER-INGRESS-v4 rule 10 set community 'additive 65000:100'", + "set policy route-map TRANSIT-EGRESS-v4 rule 100 action 'permit'", + "set policy route-map TRANSIT-EGRESS-v4 rule 100 match ip address prefix-list 'CUSTOMER-PREFIXES-v4'", + "set policy route-map TRANSIT-EGRESS-v4 rule 100 set as-path prepend '65000 65000'", + "set policy route-map UPSTREAM-INGRESS-v4 rule 10 action 'permit'", + ] + elapsed = _time_parse(Route_mapsTemplate14, lines) + assert elapsed < PARSE_BUDGET_SECONDS, ( + "Route_mapsTemplate14.parse() took %.2fs (budget %.2fs); " + "possible regression of T8609 (rm_templates regex backtracking)." + % (elapsed, PARSE_BUDGET_SECONDS) + ) + + +def test_bgp_global_14_parse_budget(): + """Realistic BGP neighbor/address-family config: parse() under 1s.""" + lines = [ + "set protocols bgp 65001 neighbor 2001:db8:abcd:1234::1 remote-as '65002'", + "set protocols bgp 65001 neighbor 2001:db8:abcd:1234::1 description 'IXP-PEER-1'", + "set protocols bgp 65001 neighbor 2001:db8:abcd:1234::1 address-family ipv6-unicast route-map import 'IXP-INGRESS-v6'", + "set protocols bgp 65001 neighbor 2001:db8:abcd:1234::1 address-family ipv6-unicast route-map export 'IXP-EGRESS-v6'", + "set protocols bgp 65001 neighbor 192.0.2.1 remote-as '65003'", + "set protocols bgp 65001 neighbor 192.0.2.1 description 'TRANSIT-PROVIDER-1'", + "set protocols bgp 65001 neighbor 192.0.2.1 address-family ipv4-unicast route-map import 'TRANSIT-INGRESS-v4'", + "set protocols bgp 65001 neighbor 192.0.2.1 address-family ipv4-unicast route-map export 'TRANSIT-EGRESS-v4'", + ] + elapsed = _time_parse(Bgp_globalTemplate14, lines) + assert elapsed < PARSE_BUDGET_SECONDS, ( + "Bgp_globalTemplate14.parse() took %.2fs (budget %.2fs); " + "possible regression of T8609 (rm_templates regex backtracking)." + % (elapsed, PARSE_BUDGET_SECONDS) + ) + + +def test_snmp_server_parse_budget(): + """Realistic SNMP v3 config: parse() under 1s.""" + lines = [ + "set service snmp community PUBLIC-COMMUNITY-NAME-1 authorization 'ro'", + "set service snmp community PUBLIC-COMMUNITY-NAME-1 client '192.0.2.0/24'", + "set service snmp v3 trap-target TRAP-TARGET-LONG-NAME-1 user 'monitor'", + "set service snmp v3 trap-target TRAP-TARGET-LONG-NAME-1 protocol 'udp'", + "set service snmp v3 trap-target TRAP-TARGET-LONG-NAME-1 port '162'", + "set service snmp v3 user TRAP-USER-LONG-NAME-1 mode 'auth'", + "set service snmp v3 user TRAP-USER-LONG-NAME-1 group 'monitor'", + ] + elapsed = _time_parse(Snmp_serverTemplate, lines) + assert elapsed < PARSE_BUDGET_SECONDS, ( + "Snmp_serverTemplate.parse() took %.2fs (budget %.2fs); " + "possible regression of T8609 (rm_templates regex backtracking)." + % (elapsed, PARSE_BUDGET_SECONDS) + ) + + +def test_bgp_address_family_14_parse_budget(): + """Realistic BGP address-family aggregate config: parse() under 1s.""" + lines = [ + "set protocols bgp 65001 address-family ipv4-unicast network 198.51.100.0/24 backdoor", + "set protocols bgp 65001 address-family ipv4-unicast network 198.51.100.0/24 path-limit '4'", + "set protocols bgp 65001 address-family ipv4-unicast network 198.51.100.0/24 route-map 'NET-IN-v4'", + "set protocols bgp 65001 address-family ipv4-unicast aggregate-address 203.0.113.0/24 as-set", + "set protocols bgp 65001 address-family ipv4-unicast aggregate-address 203.0.113.0/24 summary-only", + "set protocols bgp 65001 address-family ipv6-unicast network 2001:db8:abcd:1234::/64 backdoor", + "set protocols bgp 65001 address-family ipv6-unicast network 2001:db8:abcd:1234::/64 route-map 'NET-IN-v6'", + "set protocols bgp 65001 address-family ipv6-unicast aggregate-address 2001:db8::/32 as-set", + "set protocols bgp 65001 address-family ipv6-unicast aggregate-address 2001:db8::/32 summary-only", + ] + elapsed = _time_parse(Bgp_address_familyTemplate14, lines) + assert elapsed < PARSE_BUDGET_SECONDS, ( + "Bgp_address_familyTemplate14.parse() took %.2fs (budget %.2fs); " + "possible regression of T8609 (rm_templates regex backtracking)." + % (elapsed, PARSE_BUDGET_SECONDS) + ) + + +def test_route_maps_14_set_comm_list_delete_matches_setval(): + """Round-trip check: the `set_comm_list_delete` parser must match the line its setval generates. + + `set_comm_list_delete`'s setval emits `set policy route-map X rule N set + comm-list delete` with no token after `delete`. Pre-T8609 the getval + happened to match this by accident (a `*` quantifier on the trailing + `(?P<delete>\\S+)` made the group optional after VERBOSE-strip). An + earlier draft of T8609's fix made the group required, causing the + parser to silently ignore its own output. This test guards the + round-trip. + """ + line = "set policy route-map MY-MAP rule 10 set comm-list delete" + parser = Route_mapsTemplate14(lines=[line]) + result = parser.parse() + rm = result.get("route_maps", {}).get("MY-MAP") + assert rm is not None, ( + "route_maps_14: set_comm_list_delete parser failed to match its " + "own setval-generated line %r; the parser is broken." % line + ) + entry = rm.get("entries", {}).get(10, {}) + comm_list = entry.get("set", {}).get("comm_list", {}) + assert comm_list.get("delete"), ( + "route_maps_14: set_comm_list_delete matched the line but did not " + "populate set.comm_list.delete; check the result template." + ) diff --git a/tests/unit/modules/network/vyos/test_vyos_banner.py b/tests/unit/modules/network/vyos/test_vyos_banner.py index d418a27c..c15ed8af 100644 --- a/tests/unit/modules/network/vyos/test_vyos_banner.py +++ b/tests/unit/modules/network/vyos/test_vyos_banner.py @@ -16,7 +16,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_bgp_address_family.py b/tests/unit/modules/network/vyos/test_vyos_bgp_address_family.py index 7ea9b2cd..9d6f6297 100644 --- a/tests/unit/modules/network/vyos/test_vyos_bgp_address_family.py +++ b/tests/unit/modules/network/vyos/test_vyos_bgp_address_family.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -701,13 +700,13 @@ class TestVyosBgpafOpsModule13(TestVyosModule): ], redistribute=[ dict(protocol="ospf", metric=25), - ] + ], ), dict( afi="ipv6", redistribute=[ dict(protocol="ospfv3", metric=20), - dict(protocol="ripng") + dict(protocol="ripng"), ], ), ], @@ -738,13 +737,13 @@ class TestVyosBgpafOpsModule13(TestVyosModule): ], redistribute=[ dict(protocol="ospf", metric=25), - ] + ], ), dict( afi="ipv6", redistribute=[ dict(protocol="ospfv3", metric=20), - dict(protocol="ripng") + dict(protocol="ripng"), ], ), ], @@ -772,7 +771,7 @@ class TestVyosBgpafOpsModule13(TestVyosModule): afi="ipv4", networks=[ dict(prefix="192.2.13.0/24"), - ] + ], ), dict( afi="ipv6", diff --git a/tests/unit/modules/network/vyos/test_vyos_bgp_address_family14.py b/tests/unit/modules/network/vyos/test_vyos_bgp_address_family14.py index 9e615ff0..447e324f 100644 --- a/tests/unit/modules/network/vyos/test_vyos_bgp_address_family14.py +++ b/tests/unit/modules/network/vyos/test_vyos_bgp_address_family14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -45,13 +44,13 @@ class TestVyosBgpafModule14(TestVyosModule): ) self.execute_show_command = self.mock_execute_show_command.start() self.mock_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_address_family.bgp_address_family.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_address_family.bgp_address_family.get_os_version", ) self.test_version = "1.4" self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = self.test_version self.mock_facts_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family.get_os_version", ) self.get_facts_os_version = self.mock_facts_get_os_version.start() self.get_facts_os_version.return_value = self.test_version @@ -425,46 +424,6 @@ class TestVyosBgpafModule14(TestVyosModule): self.execute_module(changed=True, commands=commands) - # def test_vyos_bgp_address_family_incorrect_instance(self): - # set_module_args( - # dict( - # state="overridden", - # config=dict( - # as_number=100, - # address_family=[ - # dict( - # afi="ipv4", - # networks=[ - # dict(prefix="192.1.13.0/24", route_map="map01"), - # ], - # ), - # dict( - # afi="ipv6", - # redistribute=[dict(protocol="ospfv3", metric=20)], - # ), - # ], - # neighbors=[ - # dict( - # neighbor_address="192.10.21.25", - # address_family=[ - # dict( - # afi="ipv4", - # route_map=[dict(action="import", route_map="map01")], - # ), - # dict( - # afi="ipv6", - # distribute_list=[dict(action="export", acl=10)], - # route_server_client=True, - # ), - # ], - # ), - # ], - # ), - # ), - # ) - # result = self.execute_module(failed=True) - # self.assertIn("Only one bgp instance is allowed per device", result["msg"]) - def test_vyos_bgp_address_family_rendered(self): set_module_args( dict( @@ -708,7 +667,7 @@ class TestVyosBgpafModule14(TestVyosModule): ) self.execute_module(changed=False, commands=[]) - def test_vyos_bgp_address_family_replaced_asn(self): + def test_vyos_bgp_address_family_replaced_asn2(self): set_module_args( dict( state="replaced", @@ -764,7 +723,7 @@ class TestVyosBgpafModule14(TestVyosModule): ] self.execute_module(changed=True, commands=commands) - def test_vyos_bgp_address_family_overridden_asn(self): + def test_vyos_bgp_address_family_overridden_asn2(self): set_module_args( dict( state="overridden", @@ -842,13 +801,13 @@ class TestVyosBgpafOpsModule14(TestVyosModule): ) self.execute_show_command = self.mock_execute_show_command.start() self.mock_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_address_family.bgp_address_family.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_address_family.bgp_address_family.get_os_version", ) self.test_version = "1.4" self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = self.test_version self.mock_facts_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family.get_os_version", ) self.get_facts_os_version = self.mock_facts_get_os_version.start() self.get_facts_os_version.return_value = self.test_version @@ -911,13 +870,13 @@ class TestVyosBgpafOpsModule14(TestVyosModule): ], redistribute=[ dict(protocol="ospf", metric=25), - ] + ], ), dict( afi="ipv6", redistribute=[ dict(protocol="ospfv3", metric=20), - dict(protocol="ripng") + dict(protocol="ripng"), ], ), ], @@ -948,13 +907,13 @@ class TestVyosBgpafOpsModule14(TestVyosModule): ], redistribute=[ dict(protocol="ospf", metric=25), - ] + ], ), dict( afi="ipv6", redistribute=[ dict(protocol="ospfv3", metric=20), - dict(protocol="ripng") + dict(protocol="ripng"), ], ), ], @@ -982,7 +941,7 @@ class TestVyosBgpafOpsModule14(TestVyosModule): afi="ipv4", networks=[ dict(prefix="192.2.13.0/24"), - ] + ], ), dict( afi="ipv6", diff --git a/tests/unit/modules/network/vyos/test_vyos_bgp_global.py b/tests/unit/modules/network/vyos/test_vyos_bgp_global.py index e363f77b..c3680492 100644 --- a/tests/unit/modules/network/vyos/test_vyos_bgp_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_bgp_global.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_bgp_global14.py b/tests/unit/modules/network/vyos/test_vyos_bgp_global14.py index ee647616..cf4a377b 100644 --- a/tests/unit/modules/network/vyos/test_vyos_bgp_global14.py +++ b/tests/unit/modules/network/vyos/test_vyos_bgp_global14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_command.py b/tests/unit/modules/network/vyos/test_vyos_command.py index 2c7cd328..a0ad0942 100644 --- a/tests/unit/modules/network/vyos/test_vyos_command.py +++ b/tests/unit/modules/network/vyos/test_vyos_command.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_config.py b/tests/unit/modules/network/vyos/test_vyos_config.py index 4f1cac6a..e732ca60 100644 --- a/tests/unit/modules/network/vyos/test_vyos_config.py +++ b/tests/unit/modules/network/vyos/test_vyos_config.py @@ -19,7 +19,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import MagicMock, patch @@ -140,3 +139,41 @@ class TestVyosConfigModule(TestVyosModule): return_value=self.cliconf_obj.get_diff(candidate, None, diff_match="none"), ) self.execute_module(changed=True, commands=lines, sort=False) + + def test_vyos_config_confirm_automatic(self): + src = load_fixture("vyos_config_src.cfg") + confirm_timeout = 7 + set_module_args(dict(src=src, confirm="automatic", confirm_timeout=confirm_timeout)) + candidate = "\n".join(self.module.format_commands(src.splitlines())) + commands = [ + "set system host-name foo", + "delete interfaces ethernet eth0 address", + ] + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(candidate, self.running_config), + ) + + self.execute_module(changed=True, commands=commands) + + self.assertEqual(self.load_config.call_args[1]["confirm"], confirm_timeout) + self.run_commands.assert_called_once() + self.assertEqual( + ["configure", "confirm", "exit"], + self.run_commands.call_args[0][1], + ) + + def test_vyos_config_confirm_manual(self): + lines = [ + "set system host-name foo", + ] + confirm_timeout = 12 + set_module_args(dict(lines=lines, confirm="manual", confirm_timeout=confirm_timeout)) + candidate = "\n".join(lines) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(candidate, self.running_config), + ) + + self.execute_module(changed=True, commands=lines) + + self.assertEqual(self.load_config.call_args[1]["confirm"], confirm_timeout) + self.run_commands.assert_not_called() diff --git a/tests/unit/modules/network/vyos/test_vyos_facts.py b/tests/unit/modules/network/vyos/test_vyos_facts.py index 7e192e3c..3f85bf7d 100644 --- a/tests/unit/modules/network/vyos/test_vyos_facts.py +++ b/tests/unit/modules/network/vyos/test_vyos_facts.py @@ -17,7 +17,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type import json diff --git a/tests/unit/modules/network/vyos/test_vyos_file.py b/tests/unit/modules/network/vyos/test_vyos_file.py new file mode 100644 index 00000000..3ab5fbe5 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_file.py @@ -0,0 +1,524 @@ +# tests/unit/modules/network/vyos/test_vyos_file.py +# +# Mocks run_commands() directly — the real call path this module uses via +# get_connection()/run_commands() in module_utils/network/vyos/vyos.py. +# This replaces an earlier draft that mocked a bespoke ActionModule; that +# design was abandoned once it turned out every module in this collection +# (vyos_command, vyos_config, etc.) shares one generic action plugin and +# puts real logic inside main() instead. + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import hashlib +import json +import os +import tempfile + +from unittest.mock import MagicMock, patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_file +from ansible_collections.vyos.vyos.tests.unit.modules.network.vyos.vyos_module import ( + TestVyosModule, +) +from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( + AnsibleExitJson, + AnsibleFailJson, + set_module_args, +) + + +class TestVyosFileModule(TestVyosModule): + + module = vyos_file + + def setUp(self): + super(TestVyosFileModule, self).setUp() + self.mock_run_commands = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_file.run_commands", + ) + self.run_commands = self.mock_run_commands.start() + + # content/src transfer now goes through a real SCP call via + # get_connection(module).copy_file(...) — never through + # run_commands() — so it needs its own mock, separate from the + # command-based stat/chown/chmod/rm path above. + self.mock_get_connection = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_file.get_connection", + ) + self.get_connection = self.mock_get_connection.start() + self.mock_connection = MagicMock() + self.mock_connection.get_option.return_value = 30 + self.get_connection.return_value = self.mock_connection + + def tearDown(self): + super(TestVyosFileModule, self).tearDown() + self.mock_run_commands.stop() + self.mock_get_connection.stop() + + # ---- helpers ----------------------------------------------------- + + def _queue(self, *responses): + """Queue successive return values, one per run_commands() call.""" + self.run_commands.side_effect = list(responses) + + def _run(self, args, expect_fail=False): + set_module_args(args) + exc = AnsibleFailJson if expect_fail else AnsibleExitJson + with self.assertRaises(exc) as ctx: + vyos_file.main() + return ctx.exception.args[0] + + # ---- idempotency core --------------------------------------------- + + def test_creates_when_absent(self): + # get_have() issues ONE stat call; converge() batches mkdir+chown+ + # chmod into a SINGLE run_commands() call (not one call per + # command); the post-check issues one more stat call. Three total + # run_commands() invocations, matching the module's actual batching. + self._queue( + ["stat: cannot statx '/config/auth/x': No such file or directory"], + ["", "", ""], # mkdir, chown, chmod — one batched call + ["750 vyos vyattacfg 4096"], # post-check stat + ) + result = self._run( + {"dest": "/config/auth/x", "owner": "vyos", "group": "vyattacfg", "mode": "0750"}, + ) + self.assertTrue(result["changed"]) + self.assertIn("state", result["diff_fields"]) + self.assertIn("owner", result["diff_fields"]) + + def test_noop_when_converged(self): + self._queue(["750 vyos vyattacfg 4096"]) + result = self._run( + {"dest": "/config/auth/x", "owner": "vyos", "group": "vyattacfg", "mode": "0750"}, + ) + self.assertFalse(result["changed"]) + self.assertEqual(result["diff_fields"], []) + + def test_setgid_ignored_when_mode_leading_digit_is_zero(self): + # /config/auth is deliberately setgid vyattacfg (vyos.dev T2713). + # Requesting mode '0750' (leading digit 0) must NOT be reported as + # different from an actual mode of 2750. + self._queue(["2750 vyos vyattacfg 4096"]) + result = self._run({"dest": "/config/auth/x", "mode": "0750"}) + self.assertFalse(result["changed"], result.get("diff_fields")) + + def test_setgid_respected_when_explicitly_requested(self): + # Explicit non-zero leading digit means the caller does care about + # the special bits — since 2750 is requested and 2750 is already + # there, this should be a no-op (only the initial stat call fires). + self._queue(["2750 vyos vyattacfg 4096"]) + result = self._run({"dest": "/config/auth/x", "mode": "2750"}) + self.assertFalse(result["changed"]) + + def test_mode_change_detected(self): + # Only mode differs, so converge() batches a single chmod command + # (one run_commands() call), then the post-check stat is a second. + self._queue( + ["600 vyos vyattacfg 10"], + [""], # chmod — the only mutating command needed + ["640 vyos vyattacfg 10"], + ) + result = self._run({"dest": "/config/auth/x/hello.txt", "mode": "0640"}) + self.assertTrue(result["changed"]) + self.assertEqual(result["diff_fields"], ["mode"]) + + def test_mode_string_normalization(self): + # "00640" (5 digits) is deliberately excluded here — it's now + # correctly rejected by the strict [0-7]{3,4} validation (see + # test_rejects_mode_with_extra_leading_digit), even though its + # value is harmless. Only genuinely valid 3-4 digit forms of the + # same value are expected to normalize equivalently. + for requested in ("640", "0640"): + with self.subTest(requested=requested): + self._queue(["640 vyos vyattacfg 10"]) + result = self._run({"dest": "/config/auth/x/hello.txt", "mode": requested}) + self.assertFalse( + result["changed"], + "mode {0!r} incorrectly compared unequal to stat's '640'".format(requested), + ) + + def test_implicit_mode_uses_symbolic_chmod_preserving_special_bits(self): + # Real bug found in review: a plain numeric chmod ALWAYS explicitly + # sets the special-bits digit (even a bare 3-digit form implies a + # leading 0), so it would silently clear an existing setgid/setuid + # bit the moment any rwx change is needed — directly contradicting + # the "special bits are unmanaged for implicit mode" guarantee this + # module's own diff comparison already promises. Symbolic chmod + # (u=,g=,o=) is the only form that genuinely leaves them untouched. + self._queue( + ["2770 vyos vyattacfg 10"], # existing: setgid + rwxrwx--- + [""], # the single batched chmod command + ["2750 vyos vyattacfg 10"], # post-check: rwx fixed, setgid survived + ) + result = self._run({"dest": "/x", "mode": "0750"}) + self.assertTrue(result["changed"]) + self.assertEqual(result["diff_fields"], ["mode"]) + + converge_call = self.run_commands.call_args_list[1] + chmod_cmd = converge_call.args[1][0] + self.assertIn("u=", chmod_cmd, "expected symbolic chmod for an implicit mode request") + self.assertNotRegex( + chmod_cmd, + r"chmod\s+0?750\b", + "must not use a numeric chmod for an implicit mode request — it would " + "clear the existing setgid bit", + ) + + def test_explicit_mode_uses_numeric_chmod(self): + # A non-zero leading digit means the caller explicitly wants control + # over special bits too — numeric chmod is correct here, unlike the + # implicit case above. + self._queue( + ["0750 vyos vyattacfg 10"], # existing: no special bits + [""], + ["2750 vyos vyattacfg 10"], # post-check: matches the explicit request + ) + result = self._run({"dest": "/x", "mode": "2750"}) + self.assertTrue(result["changed"]) + + converge_call = self.run_commands.call_args_list[1] + chmod_cmd = converge_call.args[1][0] + self.assertIn("2750", chmod_cmd) + self.assertNotIn("u=", chmod_cmd, "explicit mode should use numeric chmod, not symbolic") + + # ---- content --------------------------------------------------------- + + def test_content_push_detected_and_verified(self): + # Content transfer now stages to a /tmp path via copy_file(), then + # relocates into `dest` via a sudo-prefixed mv (run_commands call). + # That mv is now a separate run_commands() call inserted between + # the initial stat and the chown+chmod batch. + real_hash = "98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4" + self._queue( + ["stat: cannot statx '/config/auth/x/hello.txt': No such file or directory"], + [""], # mv staging path -> dest + ["", ""], # chown, chmod — batched + ["600 vyos vyattacfg 10"], # post-check stat + ["{0} /config/auth/x/hello.txt".format(real_hash)], # post-check sha256sum + ) + result = self._run( + { + "dest": "/config/auth/x/hello.txt", + "content": "hi\n", + "owner": "vyos", + "mode": "0600", + }, + ) + self.assertTrue(result["changed"]) + self.assertIn("content", result["diff_fields"]) + self.mock_connection.copy_file.assert_called_once() + # copy_file's destination is now the /tmp staging path, NOT the + # final dest — the mv (with become applied) does the real placement. + staged_dest = self.mock_connection.copy_file.call_args.kwargs["destination"] + self.assertTrue(staged_dest.startswith("/tmp/.vyos_file_staging_")) + mv_call = self.run_commands.call_args_list[1] + mv_cmd = mv_call.args[1][0] + self.assertIn("mv", mv_cmd) + self.assertIn(staged_dest, mv_cmd) + self.assertIn("/config/auth/x/hello.txt", mv_cmd) + + def test_src_upload_reads_local_file_and_pushes_content(self): + # src takes a different code path from content (read_local_bytes() + # opens the local path rather than encoding an inline string), and + # had no direct test coverage — this exercises that path explicitly + # using a real temporary file, since local_content_hash()/ + # read_local_bytes() do plain open() calls that aren't mockable + # through run_commands. + with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as f: + f.write("-----BEGIN CERTIFICATE-----\nfakecertdata\n-----END CERTIFICATE-----\n") + local_path = f.name + try: + real_hash = hashlib.sha256( + b"-----BEGIN CERTIFICATE-----\nfakecertdata\n-----END CERTIFICATE-----\n", + ).hexdigest() + self._queue( + ["stat: cannot statx '/config/auth/x/client.pem': No such file or directory"], + [""], # mv staging path -> dest + ["", ""], # chown, chmod — batched + ["600 vyos vyattacfg 10"], + ["{0} /config/auth/x/client.pem".format(real_hash)], + ) + result = self._run( + { + "dest": "/config/auth/x/client.pem", + "src": local_path, + "owner": "vyos", + "mode": "0600", + }, + ) + self.assertTrue(result["changed"]) + self.assertIn("content", result["diff_fields"]) + self.mock_connection.copy_file.assert_called_once() + self.assertEqual( + self.mock_connection.copy_file.call_args.kwargs["source"], + local_path, + ) + finally: + os.unlink(local_path) + + def test_src_upload_idempotent_on_matching_remote_content(self): + # have already matches want entirely -> diff is empty -> converge() + # (and therefore push_content_via_scp/mv) never runs at all, so + # this stays at 1 run_commands call, unaffected by the staging change. + data = b"identical content\n" + with tempfile.NamedTemporaryFile(mode="wb", suffix=".txt", delete=False) as f: + f.write(data) + local_path = f.name + try: + real_hash = hashlib.sha256(data).hexdigest() + self._queue( + ["600 vyos vyattacfg 10"], + ["{0} /x".format(real_hash)], + ) + result = self._run({"dest": "/x", "src": local_path, "owner": "vyos", "mode": "0600"}) + self.assertFalse(result["changed"], result.get("diff_fields")) + self.mock_connection.copy_file.assert_not_called() + finally: + os.unlink(local_path) + + def test_content_hash_looked_up_only_when_relevant(self): + # plain ownership/mode management on an existing path should never + # trigger a sha256sum call — that's the whole point of the + # need_content_hash gate. + self._queue(["750 vyos vyattacfg 4096"]) + self._run({"dest": "/config/auth/x", "mode": "0750"}) + called_commands = [c.args[1] for c in self.run_commands.call_args_list] + joined = " ".join(str(c) for c in called_commands) + self.assertNotIn("sha256sum", joined) + + # ---- absent state ---------------------------------------------------- + + def test_absent_on_existing_removes(self): + self._queue( + ["600 vyos vyattacfg 10"], + [""], # rm -rf + ["stat: cannot statx '/config/auth/x/hello.txt': No such file or directory"], + ) + result = self._run({"dest": "/config/auth/x/hello.txt", "state": "absent"}) + self.assertTrue(result["changed"]) + self.assertEqual(result["diff_fields"], ["state"]) + + def test_absent_noop_when_already_gone(self): + self._queue(["stat: cannot statx '/x': No such file or directory"]) + result = self._run({"dest": "/x", "state": "absent"}) + self.assertFalse(result["changed"]) + + def test_real_stat_error_fails_loudly_instead_of_treated_as_missing(self): + # Permission denied (or any other real stat failure) must NOT be + # silently treated the same as "doesn't exist" — that could lead + # the module to attempt mkdir/chown/chmod against a path it + # actually has no real visibility into. + self._queue(["stat: cannot statx '/x': Permission denied"]) + result = self._run({"dest": "/x", "mode": "0750"}, expect_fail=True) + self.assertIn("unexpected stat output", result["msg"]) + + # ---- destination path validation --------------------------------- + + def test_rejects_relative_path(self): + # No run_commands() calls should even be attempted for an invalid + # dest — validation must happen before any stat/converge logic. + result = self._run({"dest": "relative/path"}, expect_fail=True) + self.assertIn("absolute path", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_root_path(self): + result = self._run({"dest": "/", "state": "absent"}, expect_fail=True) + self.assertIn("root filesystem", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_double_slash_root_bypass(self): + # os.path.normpath preserves "//" as-is (a POSIX quirk for exactly + # two leading slashes) rather than collapsing it to "/" — a naive + # `normalized == "/"` check would miss this and let it through. + result = self._run({"dest": "//", "state": "absent"}, expect_fail=True) + self.assertIn("root filesystem", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_dot_path_that_normalizes_to_root(self): + result = self._run({"dest": "/.", "state": "absent"}, expect_fail=True) + self.assertIn("root filesystem", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + # ---- mode validation ----------------------------------------------- + + def test_rejects_mode_with_extra_leading_digit(self): + # The exact real bug found in review: _normalize_mode()'s + # zfill(4)[-4:] would silently truncate "10640" into "0640" rather + # than rejecting an obviously malformed 5-digit value — applying + # permissions the caller never actually asked for. + result = self._run({"dest": "/x", "mode": "10640"}, expect_fail=True) + self.assertIn("octal string", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_non_octal_digits(self): + result = self._run({"dest": "/x", "mode": "0890"}, expect_fail=True) + self.assertIn("octal string", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_non_numeric_mode(self): + result = self._run({"dest": "/x", "mode": "abcd"}, expect_fail=True) + self.assertIn("octal string", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_too_short_mode(self): + result = self._run({"dest": "/x", "mode": "07"}, expect_fail=True) + self.assertIn("octal string", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_accepts_valid_3_and_4_digit_modes(self): + # Sanity check that the new strict validation doesn't reject + # legitimate input alongside the malformed cases above. + for valid_mode in ("750", "0750", "2750", "0000", "7777"): + with self.subTest(valid_mode=valid_mode): + self._queue([" ".join([valid_mode.zfill(4), "vyos", "vyattacfg", "10"])]) + result = self._run({"dest": "/x", "mode": valid_mode}) + self.assertFalse(result["changed"]) + + # ---- src validation -------------------------------------------------- + + def test_rejects_missing_src_file(self): + # Without this check, open() inside local_content_hash() would + # raise an unhandled FileNotFoundError instead of a clean module + # error — and this happens even under check_mode, since content + # hashing runs before the check-mode short-circuit. + result = self._run( + {"dest": "/x", "src": "/definitely/does/not/exist/x.pem"}, + expect_fail=True, + ) + self.assertIn("src not found", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_rejects_src_that_is_a_directory(self): + with tempfile.TemporaryDirectory() as tmpdir: + result = self._run({"dest": "/x", "src": tmpdir}, expect_fail=True) + self.assertIn("directory", result["msg"]) + self.assertEqual(self.run_commands.call_count, 0) + + def test_malformed_sha256sum_output_does_not_get_recorded_as_a_hash(self): + # If sha256sum itself errors (e.g. a race where the file vanished + # between stat and sha256sum), the garbage output must not be + # silently trusted as a real content hash — that would corrupt the + # comparison instead of surfacing as a real, visible diff. + # check_mode=True keeps this isolated to have/diff computation only, + # without needing to model a full converge cycle. + self._queue( + ["600 vyos vyattacfg 10"], + ["sha256sum: /x: No such file or directory"], + ) + set_module_args({"dest": "/x", "content": "hi\n", "_ansible_check_mode": True}) + with self.assertRaises(AnsibleExitJson) as ctx: + vyos_file.main() + result = ctx.exception.args[0] + # have.content_hash stays unset -> compared against a real want hash + # -> reported as a genuine diff, not silently accepted as converged. + self.assertIn("content", result.get("diff_fields", [])) + + # ---- silent-failure detection (the real bug this caught on hardware) -- + + def test_post_check_fails_module_when_chown_silently_no_ops(self): + # Reproduces the real failure found on hardware: chown to a + # nonexistent group prints an error but the CLI still reports the + # line as "executed" with rc 0 — have must be re-verified. + # converge() batches mkdir+chown into one call (mode wasn't + # requested, so no chmod); the second queued item represents that + # single batched call's two responses. + self._queue( + ["stat: cannot statx '/x': No such file or directory"], + ["", "chown: invalid group: 'x:bogus'"], # mkdir ok, chown failed + ["644 root nogroup 4096"], # post-check: neither owner nor group took + ) + result = self._run({"dest": "/x", "owner": "vyos", "group": "bogus"}, expect_fail=True) + self.assertIn("post-check", result["msg"]) + + # ---- check_mode -------------------------------------------------------- + + def test_check_mode_reports_diff_without_converging(self): + self._queue(["600 vyos vyattacfg 10"]) + set_module_args({"dest": "/x", "mode": "0640", "_ansible_check_mode": True}) + with self.assertRaises(AnsibleExitJson) as ctx: + vyos_file.main() + result = ctx.exception.args[0] + self.assertTrue(result["changed"]) + self.assertEqual(result["diff_fields"], ["mode"]) + # only the initial stat call should have happened — no chmod + self.assertEqual(self.run_commands.call_count, 1) + + # ---- secrets discipline ------------------------------------------------ + + def test_content_push_via_scp_creates_and_cleans_up_temp_file_for_inline_content(self): + # For `content` (no src), a real local temp file must be created to + # hand to copy_file() (SCP needs a real source path — it can't + # stream an in-memory string), and that temp file must be removed + # again afterward regardless of outcome, since it briefly holds + # secret material on the controller's local disk. + captured_path = {} + + def fake_copy_file(source, destination, proto, timeout): + captured_path["source"] = source + # the temp file must exist at the moment copy_file is invoked + self.assertTrue(os.path.exists(source)) + with open(source, "rb") as f: + self.assertEqual(f.read(), b"hi\n") + + self.mock_connection.copy_file.side_effect = fake_copy_file + self._queue( + ["stat: cannot statx '/x': No such file or directory"], + [""], # mv staging path -> dest (now unconditional, even with no owner/mode) + ["600 vyos vyattacfg 10"], + [hashlib.sha256(b"hi\n").hexdigest() + " /x"], + ) + self._run({"dest": "/x", "content": "hi\n"}) + # cleaned up after the transfer completes — nothing sensitive left + # sitting on the controller's local disk + self.assertFalse(os.path.exists(captured_path["source"])) + + def test_content_push_via_scp_uses_src_path_directly_without_a_temp_file(self): + # When src is given, the provided path IS the source — no temp + # file should be created or deleted for it. + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + f.write("real file content\n") + local_path = f.name + try: + self._queue( + ["stat: cannot statx '/x': No such file or directory"], + [""], # mv staging path -> dest + ["600 vyos vyattacfg 10"], + [hashlib.sha256(b"real file content\n").hexdigest() + " /x"], + ) + self._run({"dest": "/x", "src": local_path}) + self.assertEqual( + self.mock_connection.copy_file.call_args.kwargs["source"], + local_path, + ) + # the caller's own file must still exist — this module must + # never delete a user-provided src path + self.assertTrue(os.path.exists(local_path)) + finally: + os.unlink(local_path) + + def test_content_not_echoed_in_result(self): + # No owner/group/mode requested here, so converge()'s cmds list + # stays empty — but push_content_via_scp's mv is unconditional + # regardless, so it's still 4 run_commands calls total: initial + # stat, mv, post-check stat, post-check sha256sum. + real_hash = "03767fbe485736bb40cc5d85e4c9bb10b12a415674b46faf005aa22188a39a10" + self._queue( + ["stat: cannot statx '/x': No such file or directory"], + [""], # mv staging path -> dest + ["600 root root 4"], # post-check stat + ["{0} /x".format(real_hash)], # post-check sha256sum + ) + result = self._run({"dest": "/x", "content": "super-secret-value"}) + self.assertNotIn("super-secret-value", json.dumps(result)) + # the secret must not leak into the copy_file() call args either — + # only a real local temp-file path should appear there + for call in self.mock_connection.copy_file.call_args_list: + self.assertNotIn("super-secret-value", str(call)) + # nor into any run_commands() call — the mv only ever references + # paths (staging path, dest), never file content + for call in self.run_commands.call_args_list: + self.assertNotIn("super-secret-value", str(call)) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py index 481cc1dd..f62ed4b8 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -109,7 +108,7 @@ class TestVyosFirewallGlobalModule(TestVyosModule): ), dict( afi="ipv6", - ip_src_route=True, + ip_src_route=False, icmp_redirects=dict(receive=False), ), ], @@ -183,6 +182,7 @@ class TestVyosFirewallGlobalModule(TestVyosModule): "set firewall group port-group TELNET description 'This group has the telnet ports'", "set firewall group port-group TELNET", "set firewall ip-src-route 'enable'", + "set firewall ipv6-src-route 'disable'", "set firewall receive-redirects 'disable'", "set firewall config-trap 'enable'", "set firewall ipv6-receive-redirects 'disable'", @@ -269,6 +269,12 @@ class TestVyosFirewallGlobalModule(TestVyosModule): ], ), dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + # No members here + ), + dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", @@ -309,6 +315,7 @@ class TestVyosFirewallGlobalModule(TestVyosModule): "delete firewall send-redirects", "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", + "delete firewall group address-group DELETE-HOSTS address", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", "delete firewall group network-group RND description", @@ -376,6 +383,7 @@ class TestVyosFirewallGlobalModule(TestVyosModule): ), ) commands = [ + "delete firewall group address-group DELETE-HOSTS", "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", "delete firewall ipv6-src-route", @@ -412,6 +420,14 @@ class TestVyosFirewallGlobalModule(TestVyosModule): ], ), dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + members=[ + dict(address="1.2.3.4"), + ], + ), + dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py index aae4aa83..397ccfbd 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -109,7 +108,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ), dict( afi="ipv6", - ip_src_route=True, + ip_src_route=False, icmp_redirects=dict(receive=False), ), ], @@ -159,6 +158,31 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ), ], ), + zone=[ + dict( + name="FZP-2", + default_action="reject", + default_log=True, + description="This is the Firewall zone fzp2", + interfaces=[ + "eth2", + "lo0", + ], + local_zone=True, + sources=[ + dict( + zone="fzp5", + firewall=dict( + name="fzp1", + ipv6_name="fzp1-v6", + ), + ), + ], + intra_zone_filtering=dict( + action="accept", + ), + ), + ], ), state="merged", ), @@ -185,6 +209,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "set firewall global-options ip-src-route 'enable'", "set firewall global-options receive-redirects 'disable'", "set firewall global-options config-trap 'enable'", + "set firewall global-options ipv6-src-route 'disable'", "set firewall global-options ipv6-receive-redirects 'disable'", "set firewall global-options state-policy established action 'accept'", "set firewall global-options state-policy established log", @@ -195,6 +220,15 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "set firewall global-options twa-hazards-protection 'enable'", "set firewall global-options syn-cookies 'enable'", "set firewall global-options source-validation 'strict'", + "set firewall zone FZP-2 default-action 'reject'", + "set firewall zone FZP-2 default-log", + "set firewall zone FZP-2 description 'This is the Firewall zone fzp2'", + "set firewall zone FZP-2 from fzp5 firewall ipv6-name fzp1-v6", + "set firewall zone FZP-2 from fzp5 firewall name fzp1", + "set firewall zone FZP-2 interface eth2", + "set firewall zone FZP-2 interface lo0", + "set firewall zone FZP-2 intra-zone-filtering action accept", + "set firewall zone FZP-2 local-zone", ] self.execute_module(changed=True, commands=commands) @@ -206,6 +240,10 @@ class TestVyosFirewallRulesModule14(TestVyosModule): address_group=[ dict( afi="ipv4", + name="A-EMPTY", + ), + dict( + afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ @@ -272,6 +310,12 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ], ), dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + # No members here + ), + dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", @@ -280,6 +324,14 @@ class TestVyosFirewallRulesModule14(TestVyosModule): dict(address="fdec:2503:89d6:59b3::2"), ], ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[ + dict(address="1.2.3.4"), + ], + description="This is a new description for a address group name that is also in a zone", + ), ], network_group=[ dict( @@ -303,25 +355,64 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ), ], ), + zone=[ + dict( + name="FZP-2", + default_action="drop", + default_log=True, + description="This is the Firewall zone fzp2", + interfaces=[ + "eth2", + "lo0", + ], + local_zone=True, + sources=[ + dict( + zone="fzp5", + firewall=dict( + name="fzp1", + ipv6_name="fzp1-v6", + ), + ), + ], + intra_zone_filtering=dict( + action="accept", + ), + ), + ], ), state="replaced", ), ) commands = [ + "delete firewall group address-group A-EMPTY", "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", + "delete firewall group address-group DELETE-HOSTS address", "delete firewall global-options all-ping", "delete firewall global-options state-policy related", "delete firewall global-options ipv6-src-route", "delete firewall global-options send-redirects", + "delete firewall zone ZONE-IZF", "set firewall global-options state-policy invalid action 'reject'", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", + "set firewall group address-group ZONE-TEST description 'This is a new description for a address group name that is also in a zone'", + "delete firewall zone ZONE-TEST", "delete firewall group network-group RND description", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", + "set firewall zone FZP-2 default-action 'drop'", + "set firewall zone FZP-2 default-log", + "set firewall zone FZP-2 description 'This is the Firewall zone fzp2'", + "set firewall zone FZP-2 from fzp5 firewall ipv6-name fzp1-v6", + "set firewall zone FZP-2 from fzp5 firewall name fzp1", + "set firewall zone FZP-2 interface eth2", + "set firewall zone FZP-2 interface lo0", + "set firewall zone FZP-2 intra-zone-filtering action accept", + "set firewall zone FZP-2 local-zone", ] self.execute_module(changed=True, commands=commands) @@ -341,6 +432,10 @@ class TestVyosFirewallRulesModule14(TestVyosModule): address_group=[ dict( afi="ipv4", + name="A-EMPTY", + ), + dict( + afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ @@ -350,6 +445,21 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ], ), dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + members=[ + dict(address="1.2.3.4"), + ], + ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[ + dict(address="1.2.3.4"), + ], + ), + dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", @@ -381,6 +491,24 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ), ], ), + zone=[ + dict( + name="ZONE-TEST", + description="zone-test test description", + interfaces=["eth0.1234"], + ), + dict( + name="ZONE-IZF", + description="zone for izf and sources delete path testing", + intra_zone_filtering=dict(action="drop"), + sources=[ + dict( + zone="zone-src", + firewall=dict(name="existing-ruleset"), + ), + ], + ), + ], ), state="replaced", ), @@ -416,6 +544,14 @@ class TestVyosFirewallRulesModule14(TestVyosModule): dict(address="fdec:2503:89d6:59b3::2"), ], ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[ + dict(address="4.3.2.1"), + ], + description="This is a new description for a address group name that is also in a zone", + ), ], network_group=[ dict( @@ -439,21 +575,38 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ), ], ), + zone=[ + dict( + name="ZONE-TEST", + description="zone-test test description", + interfaces=[ + "lo", + ], + ), + ], ), state="replaced", ), ) commands = [ + "delete firewall group address-group A-EMPTY", "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", + "delete firewall group address-group ZONE-TEST address 1.2.3.4", + "delete firewall zone ZONE-IZF", "delete firewall global-options all-ping", "delete firewall global-options ipv6-src-route", "delete firewall global-options send-redirects", "set firewall global-options state-policy related action 'drop'", "delete firewall global-options state-policy related log-level", + "delete firewall group address-group DELETE-HOSTS", "set firewall global-options state-policy invalid action 'reject'", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", + "set firewall group address-group ZONE-TEST address 4.3.2.1", + "delete firewall zone ZONE-TEST interface eth0.1234", + "set firewall zone ZONE-TEST interface lo", + "set firewall group address-group ZONE-TEST description 'This is a new description for a address group name that is also in a zone'", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", @@ -465,3 +618,401 @@ class TestVyosFirewallRulesModule14(TestVyosModule): set_module_args(dict(config=dict(), state="deleted")) commands = ["delete firewall"] self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_set_03_replaced_izf_action_change(self): + set_module_args( + dict( + config=dict( + ping=dict(all=True), + route_redirects=[ + dict(ip_src_route=True, afi="ipv6"), + dict(icmp_redirects=dict(send=True), afi="ipv4"), + ], + state_policy=[ + dict(connection_type="related", action="accept", log_level="alert"), + ], + group=dict( + address_group=[ + dict(afi="ipv4", name="A-EMPTY"), + dict( + afi="ipv4", + name="RND-HOSTS", + description="This group has the Management hosts address lists", + members=[ + dict(address="192.0.2.1"), + dict(address="192.0.2.3"), + dict(address="192.0.2.5"), + ], + ), + dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv6", + name="LOCAL-v6", + description="This group has the hosts address lists of this machine", + members=[ + dict(address="::1"), + dict(address="fdec:2503:89d6:59b3::1"), + ], + ), + ], + network_group=[ + dict( + afi="ipv4", + name="RND", + description="This group has the Management network addresses", + members=[dict(address="192.0.2.0/24")], + ), + dict( + afi="ipv6", + name="UNIQUE-LOCAL-v6", + description="This group encompasses the ULA address space in IPv6", + members=[dict(address="fc00::/7")], + ), + ], + port_group=[ + dict( + name="SSH", + description="This group has the ssh ports", + members=[dict(port="22")], + ), + ], + ), + zone=[ + dict( + name="ZONE-TEST", + description="zone-test test description", + interfaces=["eth0.1234"], + ), + dict( + name="ZONE-IZF", + description="zone for izf and sources delete path testing", + intra_zone_filtering=dict(action="accept"), # was 'drop' + sources=[ + dict( + zone="zone-src", + firewall=dict(name="existing-ruleset"), + ), + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "delete firewall zone ZONE-IZF intra-zone-filtering action", + "set firewall zone ZONE-IZF intra-zone-filtering action accept", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_set_04_replaced_sources_value_change(self): + # ZONE-IZF.sources[0].firewall.name changes from 'existing-ruleset' + # (fixture) to 'new-ruleset'. Exercises _render_sources' `not opr + # and hfw` (delete) branch. Pre-fix this leaked the old value into + # the delete command. + set_module_args( + dict( + config=dict( + ping=dict(all=True), + route_redirects=[ + dict(ip_src_route=True, afi="ipv6"), + dict(icmp_redirects=dict(send=True), afi="ipv4"), + ], + state_policy=[ + dict(connection_type="related", action="accept", log_level="alert"), + ], + group=dict( + address_group=[ + dict(afi="ipv4", name="A-EMPTY"), + dict( + afi="ipv4", + name="RND-HOSTS", + description="This group has the Management hosts address lists", + members=[ + dict(address="192.0.2.1"), + dict(address="192.0.2.3"), + dict(address="192.0.2.5"), + ], + ), + dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv6", + name="LOCAL-v6", + description="This group has the hosts address lists of this machine", + members=[ + dict(address="::1"), + dict(address="fdec:2503:89d6:59b3::1"), + ], + ), + ], + network_group=[ + dict( + afi="ipv4", + name="RND", + description="This group has the Management network addresses", + members=[dict(address="192.0.2.0/24")], + ), + dict( + afi="ipv6", + name="UNIQUE-LOCAL-v6", + description="This group encompasses the ULA address space in IPv6", + members=[dict(address="fc00::/7")], + ), + ], + port_group=[ + dict( + name="SSH", + description="This group has the ssh ports", + members=[dict(port="22")], + ), + ], + ), + zone=[ + dict( + name="ZONE-TEST", + description="zone-test test description", + interfaces=["eth0.1234"], + ), + dict( + name="ZONE-IZF", + description="zone for izf and sources delete path testing", + intra_zone_filtering=dict(action="drop"), + sources=[ + dict( + zone="zone-src", + firewall=dict(name="new-ruleset"), # was 'existing-ruleset' + ), + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "delete firewall zone ZONE-IZF from zone-src firewall name", + "set firewall zone ZONE-IZF from zone-src firewall name new-ruleset", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_set_05_replaced_sources_firewall_cleared(self): + # ZONE-IZF.sources[0] keeps the 'zone-src' key but drops the + # 'firewall' sub-dict entirely. Exercises _render_sources' `not opr + # and not hfw` branch -- unreachable before the fix, since it was + # guarded by `not have` instead of `not hfw`, and `have` can never + # be empty here (we're already inside `if zone in have_index`). + set_module_args( + dict( + config=dict( + ping=dict(all=True), + route_redirects=[ + dict(ip_src_route=True, afi="ipv6"), + dict(icmp_redirects=dict(send=True), afi="ipv4"), + ], + state_policy=[ + dict(connection_type="related", action="accept", log_level="alert"), + ], + group=dict( + address_group=[ + dict(afi="ipv4", name="A-EMPTY"), + dict( + afi="ipv4", + name="RND-HOSTS", + description="This group has the Management hosts address lists", + members=[ + dict(address="192.0.2.1"), + dict(address="192.0.2.3"), + dict(address="192.0.2.5"), + ], + ), + dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv6", + name="LOCAL-v6", + description="This group has the hosts address lists of this machine", + members=[ + dict(address="::1"), + dict(address="fdec:2503:89d6:59b3::1"), + ], + ), + ], + network_group=[ + dict( + afi="ipv4", + name="RND", + description="This group has the Management network addresses", + members=[dict(address="192.0.2.0/24")], + ), + dict( + afi="ipv6", + name="UNIQUE-LOCAL-v6", + description="This group encompasses the ULA address space in IPv6", + members=[dict(address="fc00::/7")], + ), + ], + port_group=[ + dict( + name="SSH", + description="This group has the ssh ports", + members=[dict(port="22")], + ), + ], + ), + zone=[ + dict( + name="ZONE-TEST", + description="zone-test test description", + interfaces=["eth0.1234"], + ), + dict( + name="ZONE-IZF", + description="zone for izf and sources delete path testing", + intra_zone_filtering=dict(action="drop"), + sources=[ + dict(zone="zone-src"), # firewall dropped entirely + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "delete firewall zone ZONE-IZF from zone-src", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_set_06_replaced_sources_entry_removed(self): + # ZONE-IZF's sources list drops the 'zone-src' entry entirely (not + # just its firewall sub-dict). Exercises the new `elif not opr:` + # branch -- previously this case emitted nothing at all. + set_module_args( + dict( + config=dict( + ping=dict(all=True), + route_redirects=[ + dict(ip_src_route=True, afi="ipv6"), + dict(icmp_redirects=dict(send=True), afi="ipv4"), + ], + state_policy=[ + dict(connection_type="related", action="accept", log_level="alert"), + ], + group=dict( + address_group=[ + dict(afi="ipv4", name="A-EMPTY"), + dict( + afi="ipv4", + name="RND-HOSTS", + description="This group has the Management hosts address lists", + members=[ + dict(address="192.0.2.1"), + dict(address="192.0.2.3"), + dict(address="192.0.2.5"), + ], + ), + dict( + afi="ipv4", + name="DELETE-HOSTS", + description="The (single) last address from this group will be deleted in the tests", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv4", + name="ZONE-TEST", + members=[dict(address="1.2.3.4")], + ), + dict( + afi="ipv6", + name="LOCAL-v6", + description="This group has the hosts address lists of this machine", + members=[ + dict(address="::1"), + dict(address="fdec:2503:89d6:59b3::1"), + ], + ), + ], + network_group=[ + dict( + afi="ipv4", + name="RND", + description="This group has the Management network addresses", + members=[dict(address="192.0.2.0/24")], + ), + dict( + afi="ipv6", + name="UNIQUE-LOCAL-v6", + description="This group encompasses the ULA address space in IPv6", + members=[dict(address="fc00::/7")], + ), + ], + port_group=[ + dict( + name="SSH", + description="This group has the ssh ports", + members=[dict(port="22")], + ), + ], + ), + zone=[ + dict( + name="ZONE-TEST", + description="zone-test test description", + interfaces=["eth0.1234"], + ), + dict( + name="ZONE-IZF", + description="zone for izf and sources delete path testing", + intra_zone_filtering=dict(action="drop"), + # sources omitted entirely + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "delete firewall zone ZONE-IZF from zone-src", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_ruleset_lines_filtered_from_facts(self): + # Confirms render_config's pre-filter strips all three ruleset-line + # prefixes (IPv4 name, legacy hyphenated ipv6-name, and 1.4+ + # space-separated "ipv6 name") before zone/global-options parsing + # runs, on a real 1.4.x-shaped fixture alongside existing zone data. + set_module_args(dict(config=dict(), state="gathered")) + result = self.execute_module(changed=False) + facts = result["gathered"] + self.assertNotIn("TESTRULESET-V4", str(facts)) + self.assertNotIn("TESTRULESET-V6-LEGACY", str(facts)) + self.assertNotIn("TESTRULESET-V6-1_4PLUS", str(facts)) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global15.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global15.py new file mode 100644 index 00000000..22ab5364 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global15.py @@ -0,0 +1,132 @@ +# (c) 2016 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/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_global +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosFirewallRulesModule15(TestVyosModule): + module = vyos_firewall_global + + def setUp(self): + super(TestVyosFirewallRulesModule15, self).setUp() + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.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.firewall_global.firewall_global.Firewall_globalFacts.get_device_data", + ) + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version", + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.5" + + self.execute_show_command = self.mock_execute_show_command.start() + self.maxDiff = None + + def tearDown(self): + super(TestVyosFirewallRulesModule15, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + + def load_fixtures(self, commands=None, filename=None): + def load_from_file(*args, **kwargs): + return load_fixture("vyos_firewall_global_config_v15.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_firewall_global_set_01_merged_interface_uses_member_keyword(self): + # On a 1.5.0 device (empty fixture, no existing zone), merging a + # zone with an interface should render "member interface", not the + # bare "interface" used on 1.4.x / 1.5-rolling. This is the sole + # thing this class exists to prove right now -- the version-gate + # added to _render_interfaces. + set_module_args( + dict( + config=dict( + zone=[ + dict( + name="ZONE-15", + interfaces=["eth1"], + ), + ], + ), + state="merged", + ), + ) + commands = [ + "set firewall zone ZONE-15 default-action 'drop'", + "set firewall zone ZONE-15 member interface eth1", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_set_02_gathered_member_interface_parsed(self): + # ZONE-15-EXISTING in the fixture uses 1.5.0's "member interface" + # syntax. Before the parse_zone fix, this misparsed into a bogus + # zone['member'] = "interface eth2" string field, and 'interfaces' + # was missing entirely. After the fix, it should land in + # 'interfaces' exactly like the pre-1.5.0 bare "interface" form. + set_module_args(dict(config=dict(), state="gathered")) + result = self.execute_module(changed=False) + zones = result["gathered"]["zone"] + zone = next(z for z in zones if z["name"] == "ZONE-15-EXISTING") + self.assertEqual(zone["interfaces"], ["eth2"]) + self.assertEqual(zone["description"], "existing 1.5.0 zone for facts parsing test") + self.assertNotIn("member", zone) + + def test_vyos_firewall_global_ruleset_lines_filtered_from_facts(self): + # Same coverage as the 1.4 version, confirmed independently on the + # 1.5.0 fixture/version path. + set_module_args(dict(config=dict(), state="gathered")) + result = self.execute_module(changed=False) + facts = result["gathered"] + self.assertNotIn("TESTRULESET-V4", str(facts)) + self.assertNotIn("TESTRULESET-V6-LEGACY", str(facts)) + self.assertNotIn("TESTRULESET-V6-1_4PLUS", str(facts)) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py index f921c508..2b995652 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -437,19 +436,19 @@ class TestVyosFirewallInterfacesModule(TestVyosModule): ), ) commands = [ - 'delete interfaces ethernet eth0 firewall in name', - 'delete interfaces ethernet eth0 firewall local name', - 'delete interfaces ethernet eth0 firewall out name', - 'delete interfaces ethernet eth0 firewall local ipv6-name', - 'delete interfaces ethernet eth2 firewall in name', - 'delete interfaces ethernet eth2 firewall local name', - 'delete interfaces ethernet eth2 firewall out name', - 'delete interfaces ethernet eth2 firewall local ipv6-name', + "delete interfaces ethernet eth0 firewall in name", + "delete interfaces ethernet eth0 firewall local name", + "delete interfaces ethernet eth0 firewall out name", + "delete interfaces ethernet eth0 firewall local ipv6-name", + "delete interfaces ethernet eth2 firewall in name", + "delete interfaces ethernet eth2 firewall local name", + "delete interfaces ethernet eth2 firewall out name", + "delete interfaces ethernet eth2 firewall local ipv6-name", "set interfaces ethernet eth0 vif 100 firewall in name 'INBOUND'", "set interfaces ethernet eth0 vif 100 firewall local ipv6-name 'V6-LOCAL'", "set interfaces bonding bond2 firewall local name 'LOCAL'", "set interfaces bonding bond2 firewall local ipv6-name 'V6-LOCAL'", "set interfaces wireguard wg4 firewall local name 'LOCAL'", - "set interfaces wireguard wg4 firewall local ipv6-name 'V6-LOCAL'" + "set interfaces wireguard wg4 firewall local ipv6-name 'V6-LOCAL'", ] self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_rules13.py b/tests/unit/modules/network/vyos/test_vyos_firewall_rules13.py index 9a25198f..457fc76b 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_rules13.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_rules13.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -167,6 +166,13 @@ class TestVyosFirewallRulesModule13(TestVyosModule): fragment="match-frag", disable=True, ), + dict( + number="105", + action="offload", + offload_target="test", + description="Rule 105 is configured by Ansible", + protocol="udp", + ), ], ), ], @@ -187,12 +193,17 @@ class TestVyosFirewallRulesModule13(TestVyosModule): "set firewall name INBOUND rule 101 action 'accept'", "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", "set firewall name INBOUND rule 101 log 'disable'", + "set firewall name INBOUND rule 105", + "set firewall name INBOUND rule 105 action 'offload'", + "set firewall name INBOUND rule 105 offload-target 'test'", + "set firewall name INBOUND rule 105 description 'Rule 105 is configured by Ansible'", + "set firewall name INBOUND rule 105 protocol 'udp'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -253,7 +264,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_03(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -302,7 +313,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_04(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -327,9 +338,8 @@ class TestVyosFirewallRulesModule13(TestVyosModule): tcp=dict( flags=[ dict(flag="all"), - ] + ], ), - ), ], ), @@ -399,7 +409,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_02(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -460,7 +470,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_03(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -509,7 +519,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -534,7 +544,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): tcp=dict( flags=[ dict(flag="all"), - ] + ], ), ), dict( @@ -545,8 +555,8 @@ class TestVyosFirewallRulesModule13(TestVyosModule): dict(flag="syn"), dict(flag="fin", invert=True), ], - ) - ) + ), + ), ], ), ], @@ -572,7 +582,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_icmp_01(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -605,7 +615,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_01(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -639,7 +649,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_02(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -671,8 +681,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_del_01(self): - """Test if plugin correctly removes existing rule set - """ + """Test if plugin correctly removes existing rule set""" set_module_args( dict( config=[dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")])], @@ -683,33 +692,32 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_02(self): - """Test if plugin correctly removes existing rule sets, both ipv4 and ipv6 - """ + """Test if plugin correctly removes existing rule sets, both ipv4 and ipv6""" set_module_args( dict( config=[ dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")]), dict(afi="ipv6", rule_sets=[dict(name="V6-INGRESS")]), + dict(afi="ipv4", rule_sets=[dict(name="V4-OFFLOAD")]), ], state="deleted", ), ) commands = [ + "delete firewall name V4-OFFLOAD", "delete firewall name V4-INGRESS", "delete firewall ipv6-name V6-INGRESS", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_03(self): - """Test if plugin correctly removes existing AFIs, both ipv4 and ipv6 - """ + """Test if plugin correctly removes existing AFIs, both ipv4 and ipv6""" set_module_args(dict(config=[], state="deleted")) commands = ["delete firewall name", "delete firewall ipv6-name"] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_04(self): - """Test if plugin has no effect on non-existent rule sets - """ + """Test if plugin has no effect on non-existent rule sets""" set_module_args( dict( config=[ @@ -723,7 +731,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4v6_rule_sets_rule_rep_01(self): """Test if plugin correctly replaces a particular rule set(s) - without affecting the others + without affecting the others """ set_module_args( dict( @@ -803,7 +811,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4v6_rule_sets_rule_rep_02(self): """Test if plugin correctly replaces a particular rule(s) and rule set attribute(s) - without affecting the others + without affecting the others """ set_module_args( dict( @@ -867,8 +875,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_01(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -890,7 +897,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): fragment="match-frag", disable=True, log="enable", - ) + ), ], ), dict( @@ -925,8 +932,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_02(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -947,7 +953,33 @@ class TestVyosFirewallRulesModule13(TestVyosModule): protocol="icmp", fragment="match-frag", disable=True, - log="enable" + log="enable", + ), + ], + ), + ], + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_03(self): + """Test if plugin correctly has no effect if there is no change in the configuration""" + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-OFFLOAD", + rules=[ + dict( + number="1", + action="offload", + offload_target="test", ), ], ), @@ -960,8 +992,63 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_01(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 V4-INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + fragment="match-frag", + disable=True, + ), + ], + ), + dict( + name="EGRESS", + default_action="reject", + ), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict( + name="V6-INGRESS", + default_action="accept", + ), + dict( + name="EGRESS", + default_action="reject", + rules=[ + dict( + icmp=dict(type_name="echo-request"), + number=20, + ), + ], + ), + ], + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_02(self): + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -982,13 +1069,23 @@ class TestVyosFirewallRulesModule13(TestVyosModule): protocol="icmp", fragment="match-frag", disable=True, - ) + ), ], ), dict( name="EGRESS", default_action="reject", ), + dict( + name="V4-OFFLOAD", + rules=[ + dict( + number="1", + action="offload", + offload_target="test", + ), + ], + ), ], ), dict( @@ -1017,8 +1114,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_01(self): - """Test if plugin correctly resets the entire rule set if there is a change in the configuration - """ + """Test if plugin correctly resets the entire rule set if there is a change in the configuration""" set_module_args( dict( config=[ @@ -1095,6 +1191,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): "delete firewall name V4-INGRESS", "delete firewall name EGRESS", "delete firewall name MULTIPLE-RULE", + "delete firewall name V4-OFFLOAD", "set firewall name MULTIPLE-RULE default-action 'drop'", "set firewall name MULTIPLE-RULE rule 1", "set firewall name MULTIPLE-RULE rule 1 action 'accept'", @@ -1126,7 +1223,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_v4v6_rule_sets_rule_ovr_02(self): """Test if plugin correctly resets the entire rule set - while removing the absent ones if there is a change in the configuration + while removing the absent ones if there is a change in the configuration """ set_module_args( dict( @@ -1176,6 +1273,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): "delete firewall name V4-INGRESS", "delete firewall name EGRESS", "delete firewall name MULTIPLE-RULE", + "delete firewall name V4-OFFLOAD", "set firewall name V4-INGRESS rule 101", "set firewall name V4-INGRESS description 'This is IPv4 INGRESS rule set'", "set firewall name V4-INGRESS default-action 'accept'", @@ -1186,13 +1284,12 @@ class TestVyosFirewallRulesModule13(TestVyosModule): "set firewall ipv6-name EGRESS default-action 'reject'", "set firewall ipv6-name EGRESS rule 20", "set firewall ipv6-name EGRESS rule 20 protocol 'udp'", - "set firewall ipv6-name EGRESS rule 20 action 'accept'" + "set firewall ipv6-name EGRESS rule 20 action 'accept'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_idem_01(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -1214,7 +1311,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): fragment="match-frag", disable=True, log="enable", - ) + ), ], ), dict( @@ -1237,6 +1334,16 @@ class TestVyosFirewallRulesModule13(TestVyosModule): ), ], ), + dict( + name="V4-OFFLOAD", + rules=[ + dict( + number="1", + action="offload", + offload_target="test", + ), + ], + ), ], ), dict( @@ -1265,8 +1372,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v6_rule_sets_rule_merged_01_version(self): - """Test if plugin correctly adds ipv6 rule set with rules - """ + """Test if plugin correctly adds ipv6 rule set with rules""" self.get_os_version.return_value = "1.3" set_module_args( dict( @@ -1321,13 +1427,12 @@ class TestVyosFirewallRulesModule13(TestVyosModule): "set firewall ipv6-name INBOUND rule 102 action 'reject'", "set firewall ipv6-name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", "set firewall ipv6-name INBOUND rule 102 protocol 'ipv6-icmp'", - 'set firewall ipv6-name INBOUND rule 102 icmpv6 type 7', + "set firewall ipv6-name INBOUND rule 102 icmpv6 type 7", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_jump_rules_merged_01(self): - """Test if plugin correctly adds rule set with a jump action - """ + """Test if plugin correctly adds rule set with a jump action""" set_module_args( dict( config=[ @@ -1348,7 +1453,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): protocol="icmp", icmp=dict(type_name="echo-request"), jump_target="PROTECT-RE", - packet_length_exclude=[dict(length=100), dict(length=200)] + packet_length_exclude=[dict(length=100), dict(length=200)], ), dict( number="102", @@ -1360,10 +1465,10 @@ class TestVyosFirewallRulesModule13(TestVyosModule): ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6-name INBOUND default-action 'accept'", @@ -1382,7 +1487,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): "set firewall ipv6-name INBOUND rule 102 action 'reject'", "set firewall ipv6-name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", "set firewall ipv6-name INBOUND rule 102 protocol 'ipv6-icmp'", - 'set firewall ipv6-name INBOUND rule 102 icmpv6 type 7', + "set firewall ipv6-name INBOUND rule 102 icmpv6 type 7", ] self.execute_module(changed=True, commands=commands) @@ -1409,10 +1514,10 @@ class TestVyosFirewallRulesModule13(TestVyosModule): ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6-name INBOUND default-action 'accept'", @@ -1428,7 +1533,7 @@ class TestVyosFirewallRulesModule13(TestVyosModule): def test_vyos_firewall_log_replace_01(self): """Test that stanza is correctly replaced - without touching the other stanzas + without touching the other stanzas """ set_module_args( dict( @@ -1453,10 +1558,10 @@ class TestVyosFirewallRulesModule13(TestVyosModule): ], ), ], - ) + ), ], state="replaced", - ) + ), ) commands = [ "delete firewall name V4-INGRESS rule 101", @@ -1470,3 +1575,17 @@ class TestVyosFirewallRulesModule13(TestVyosModule): ] self.maxDiff = None self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_rules_parsed_icmp_type_code(self): + """parse_icmp_attr: legacy 'type/code' token parses into integer type and code.""" + raw = ( + "set firewall name TEST rule 1 action 'accept'\n" + "set firewall name TEST rule 1 protocol 'icmp'\n" + "set firewall name TEST rule 1 icmp type '3/4'\n" + ) + set_module_args(dict(running_config=raw, state="parsed")) + result = self.execute_module(changed=False) + parsed = result["parsed"] + rule = parsed[0]["rule_sets"][0]["rules"][0] + self.assertEqual(rule["icmp"]["type"], 3) + self.assertEqual(rule["icmp"]["code"], 4) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_rules14.py b/tests/unit/modules/network/vyos/test_vyos_firewall_rules14.py index 64884282..67ed216e 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_rules14.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_rules14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -35,35 +34,35 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def setUp(self): super(TestVyosFirewallRulesModule14, self).setUp() self.mock_get_config = patch( - "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( - "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( - "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.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" + "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.static_routes.static_routes.Static_routesFacts.get_device_data" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data", ) self.mock_execute_show_command = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data", ) self.execute_show_command = self.mock_execute_show_command.start() self.mock_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.get_os_version", ) self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = "1.4" @@ -164,15 +163,15 @@ class TestVyosFirewallRulesModule14(TestVyosModule): description="Rule 101 is configured by Ansible", jump_target="PROTECT-RE", packet_length_exclude=[dict(length=100), dict(length=200)], - packet_length=[dict(length=22)] + packet_length=[dict(length=22)], ), ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6 name INBOUND default-action 'accept'", @@ -191,7 +190,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_packet_length_replace_01(self): """Test that stanza is correctly replaced - without touching the other stanzas + without touching the other stanzas """ set_module_args( dict( @@ -210,15 +209,15 @@ class TestVyosFirewallRulesModule14(TestVyosModule): action="accept", description="Rule 101 is configured by Ansible", packet_length_exclude=[dict(length=100), dict(length=200)], - packet_length=[dict(length=22)] + packet_length=[dict(length=22)], ), ], ), ], - ) + ), ], state="replaced", - ) + ), ) commands = [ "delete firewall ipv4 name V4-INGRESS rule 101", @@ -252,15 +251,15 @@ class TestVyosFirewallRulesModule14(TestVyosModule): description="Rule 101 is configured by Ansible", jump_target="PROTECT-RE", packet_length_exclude=[dict(length=100), dict(length=200)], - packet_length=[dict(length=22)] + packet_length=[dict(length=22)], ), ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6 input filter default-action 'accept'", @@ -298,13 +297,20 @@ class TestVyosFirewallRulesModule14(TestVyosModule): inbound_interface=dict(name="eth0"), outbound_interface=dict(group="eth1"), ), + dict( + number="105", + action="offload", + offload_target="test", + description="Rule 105 is configured by Ansible", + protocol="udp", + ), ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6 name V6-INGRESS description 'This is IPv6 INBOUND rule set with a jump action'", @@ -314,13 +320,18 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "set firewall ipv6 name V6-INGRESS rule 101", "set firewall ipv6 name V6-INGRESS rule 101 action 'jump'", "set firewall ipv6 name V6-INGRESS rule 101 jump-target 'PROTECT-RE'", + "set firewall ipv6 name V6-INGRESS rule 105", + "set firewall ipv6 name V6-INGRESS rule 105 action 'offload'", + "set firewall ipv6 name V6-INGRESS rule 105 offload-target 'test'", + "set firewall ipv6 name V6-INGRESS rule 105 description 'Rule 105 is configured by Ansible'", + "set firewall ipv6 name V6-INGRESS rule 105 protocol 'udp'", ] self.maxDiff = None self.execute_module(changed=True, commands=commands) def test_vyos_firewall_interface_replace_02(self): """Test that new stanza is correctly replaced - without touching the other stanzas + without touching the other stanzas """ set_module_args( dict( @@ -341,17 +352,17 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ], ), ], - ) + ), ], state="replaced", - ) + ), ) commands = [ "delete firewall ipv4 name IF-TEST rule 10", "set firewall ipv4 name IF-TEST rule 10", "set firewall ipv4 name IF-TEST description 'Changed'", "set firewall ipv4 name IF-TEST rule 10 description 'Rule 10 is configured by Ansible'", - 'set firewall ipv4 name IF-TEST rule 10 inbound-interface name eth1', + "set firewall ipv4 name IF-TEST rule 10 inbound-interface name eth1", "set firewall ipv4 name IF-TEST rule 10 action 'accept'", ] self.maxDiff = None @@ -405,7 +416,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): """Test that a rule set is correctly applied - including variant attributes such as state + including variant attributes such as state """ set_module_args( dict( @@ -466,7 +477,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_03(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -515,7 +526,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_04(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -540,9 +551,8 @@ class TestVyosFirewallRulesModule14(TestVyosModule): tcp=dict( flags=[ dict(flag="all"), - ] + ], ), - ), ], ), @@ -612,7 +622,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_02(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -673,7 +683,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_03(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -722,7 +732,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): """Test that the plugin correctly applies configuration - within exsiting rule set + within exsiting rule set """ set_module_args( dict( @@ -747,7 +757,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): tcp=dict( flags=[ dict(flag="all"), - ] + ], ), ), dict( @@ -758,8 +768,8 @@ class TestVyosFirewallRulesModule14(TestVyosModule): dict(flag="syn"), dict(flag="fin", invert=True), ], - ) - ) + ), + ), ], ), ], @@ -787,7 +797,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4_rule_sets_change_state_01(self): """Test that a rule set is replaced applied without touching the other stanzas - in particular variant attributes such as state + in particular variant attributes such as state """ set_module_args( dict( @@ -826,7 +836,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v6_rule_sets_rule_merged_icmp_01(self): """Test if plugin correctly adds new rules with variant attributes - within existing ipv6 rule set + within existing ipv6 rule set """ set_module_args( dict( @@ -859,7 +869,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_01(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -893,7 +903,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_02(self): """Test if plugin correctly adds new rules with variant attributes - within existing rule set + within existing rule set """ set_module_args( dict( @@ -925,8 +935,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_del_01(self): - """Test if plugin correctly removes existing rule set - """ + """Test if plugin correctly removes existing rule set""" set_module_args( dict( config=[dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")])], @@ -937,8 +946,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_02(self): - """Test if plugin correctly removes existing rule sets, both ipv4 and ipv6 - """ + """Test if plugin correctly removes existing rule sets, both ipv4 and ipv6""" set_module_args( dict( config=[ @@ -956,15 +964,14 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4v6_rule_sets_del_03(self): """Test that the plugin correctly deprovisions - variant configuration + variant configuration """ set_module_args(dict(config=[], state="deleted")) commands = ["delete firewall ipv4", "delete firewall ipv6"] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_04(self): - """Test if plugin has no effect on non-existent rule sets - """ + """Test if plugin has no effect on non-existent rule sets""" set_module_args( dict( config=[ @@ -976,9 +983,20 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ) self.execute_module(changed=False, commands=[]) + def test_vyos_firewall_v4_rule_sets_del_05(self): + """Test if plugin correctly removes existing rule set""" + set_module_args( + dict( + config=[dict(afi="ipv4", rule_sets=[dict(name="V4-OFFLOAD")])], + state="deleted", + ), + ) + commands = ["delete firewall ipv4 name V4-OFFLOAD"] + self.execute_module(changed=True, commands=commands) + def test_vyos_firewall_v4v6_rule_sets_rule_rep_01(self): """Test if plugin correctly replaces a particular rule set(s) - without affecting the others + without affecting the others """ set_module_args( dict( @@ -1058,7 +1076,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4v6_rule_sets_rule_rep_02(self): """Test if plugin correctly replaces a particular rule(s) and rule set attribute(s) - without affecting the others + without affecting the others """ set_module_args( dict( @@ -1122,8 +1140,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_01(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -1144,7 +1161,17 @@ class TestVyosFirewallRulesModule14(TestVyosModule): protocol="icmp", disable=True, log="enable", - ) + ), + ], + ), + dict( + name="V4-OFFLOAD", + rules=[ + dict( + number="105", + action="offload", + offload_target="test", + ), ], ), dict( @@ -1178,7 +1205,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): inbound_interface=dict(name="eth0"), outbound_interface=dict(group="the-ethers"), disable=True, - ) + ), ], ), dict( @@ -1233,8 +1260,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_02(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -1255,7 +1281,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): protocol="icmp", disable=True, log="enable", - ) + ), ], ), ], @@ -1267,8 +1293,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_01(self): - """Test if plugin correctly has no effect if there is no change in the configuration - """ + """Test if plugin correctly has no effect if there is no change in the configuration""" set_module_args( dict( config=[ @@ -1289,7 +1314,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): protocol="icmp", disable=True, log="enable", - ) + ), ], ), dict( @@ -1323,7 +1348,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): inbound_interface=dict(name="eth0"), outbound_interface=dict(group="the-ethers"), disable=True, - ) + ), ], ), dict( @@ -1378,8 +1403,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_01(self): - """Test if plugin correctly resets the entire rule set if there is a change in the configuration - """ + """Test if plugin correctly resets the entire rule set if there is a change in the configuration""" set_module_args( dict( config=[ @@ -1461,6 +1485,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "delete firewall ipv6 input filter", "delete firewall ipv6 output filter", "delete firewall ipv4 name IF-TEST", + "delete firewall ipv4 name V4-OFFLOAD", "set firewall ipv4 name MULTIPLE-RULE default-action 'drop'", "set firewall ipv4 name MULTIPLE-RULE rule 1", "set firewall ipv4 name MULTIPLE-RULE rule 1 action 'accept'", @@ -1492,7 +1517,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_v4v6_rule_sets_rule_ovr_02(self): """Test that the plugin correctly resets the entire - rule sets configuration if changes are detected + rule sets configuration if changes are detected """ set_module_args( dict( @@ -1547,6 +1572,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "delete firewall ipv6 output filter", "delete firewall ipv4 name IF-TEST", "delete firewall ipv4 name MULTIPLE-RULE", + "delete firewall ipv4 name V4-OFFLOAD", "set firewall ipv4 name V4-INGRESS rule 101", "set firewall ipv4 name V4-INGRESS default-log", "set firewall ipv4 name V4-INGRESS description 'This is IPv4 INGRESS rule set'", @@ -1557,13 +1583,13 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "set firewall ipv6 name EGRESS default-action 'reject'", "set firewall ipv6 name EGRESS rule 20", "set firewall ipv6 name EGRESS rule 20 protocol 'udp'", - "set firewall ipv6 name EGRESS rule 20 action 'accept'" + "set firewall ipv6 name EGRESS rule 20 action 'accept'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_idem_01(self): """Test that the plugin is idempotent in overridden state - if there are no changes to the rule sets + if there are no changes to the rule sets """ set_module_args( dict( @@ -1585,7 +1611,17 @@ class TestVyosFirewallRulesModule14(TestVyosModule): protocol="icmp", disable=True, log="enable", - ) + ), + ], + ), + dict( + name="V4-OFFLOAD", + rules=[ + dict( + number="105", + action="offload", + offload_target="test", + ), ], ), dict( @@ -1619,7 +1655,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): inbound_interface=dict(name="eth0"), outbound_interface=dict(group="the-ethers"), disable=True, - ) + ), ], ), dict( @@ -1690,8 +1726,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v6_rule_sets_rule_merged_01_version(self): - """Test if plugin correctly adds ipv6 rule set with rules - """ + """Test if plugin correctly adds ipv6 rule set with rules""" set_module_args( dict( config=[ @@ -1745,13 +1780,12 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "set firewall ipv6 name INBOUND rule 102 action 'reject'", "set firewall ipv6 name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", "set firewall ipv6 name INBOUND rule 102 protocol 'ipv6-icmp'", - 'set firewall ipv6 name INBOUND rule 102 icmpv6 type 7', + "set firewall ipv6 name INBOUND rule 102 icmpv6 type 7", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_jump_rules_merged_01(self): - """Test if plugin correctly adds rule set with a jump action - """ + """Test if plugin correctly adds rule set with a jump action""" set_module_args( dict( config=[ @@ -1772,7 +1806,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): protocol="icmp", icmp=dict(type_name="echo-request"), jump_target="PROTECT-RE", - packet_length_exclude=[dict(length=100), dict(length=200)] + packet_length_exclude=[dict(length=100), dict(length=200)], ), dict( number="102", @@ -1784,10 +1818,10 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6 name INBOUND default-action 'accept'", @@ -1806,7 +1840,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): "set firewall ipv6 name INBOUND rule 102 action 'reject'", "set firewall ipv6 name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", "set firewall ipv6 name INBOUND rule 102 protocol 'ipv6-icmp'", - 'set firewall ipv6 name INBOUND rule 102 icmpv6 type 7', + "set firewall ipv6 name INBOUND rule 102 icmpv6 type 7", ] self.execute_module(changed=True, commands=commands) @@ -1833,10 +1867,10 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ], ), ], - ) + ), ], state="merged", - ) + ), ) commands = [ "set firewall ipv6 name INBOUND default-action 'accept'", @@ -1852,7 +1886,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule): def test_vyos_firewall_log_replace_01(self): """Test that stanza is correctly replaced - without touching the other stanzas + without touching the other stanzas """ set_module_args( dict( @@ -1877,10 +1911,10 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ], ), ], - ) + ), ], state="replaced", - ) + ), ) commands = [ "delete firewall ipv4 name V4-INGRESS rule 101", @@ -1894,3 +1928,65 @@ class TestVyosFirewallRulesModule14(TestVyosModule): ] self.maxDiff = None self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_default_action_merged_01(self): + """Test that every default-action value is accepted and rendered""" + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-DROP", + description="Rule set configured with default_action drop", + default_action="drop", + ), + dict( + name="V4-JUMP", + description="Rule set configured with default_action jump", + default_action="jump", + default_jump_target="V4-ACCEPT", + ), + dict( + name="V4-REJECT", + description="Rule set configured with default_action reject", + default_action="reject", + ), + dict( + name="V4-RETURN", + description="Rule set configured with default_action return", + default_action="return", + ), + dict( + name="V4-ACCEPT", + description="Rule set configured with default_action accept", + default_action="accept", + ), + dict( + name="V4-CONTINUE", + description="Rule set configured with default_action continue", + default_action="continue", + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set firewall ipv4 name V4-DROP default-action 'drop'", + "set firewall ipv4 name V4-DROP description 'Rule set configured with default_action drop'", + "set firewall ipv4 name V4-JUMP default-action 'jump'", + "set firewall ipv4 name V4-JUMP default-jump-target 'V4-ACCEPT'", + "set firewall ipv4 name V4-JUMP description 'Rule set configured with default_action jump'", + "set firewall ipv4 name V4-REJECT default-action 'reject'", + "set firewall ipv4 name V4-REJECT description 'Rule set configured with default_action reject'", + "set firewall ipv4 name V4-RETURN default-action 'return'", + "set firewall ipv4 name V4-RETURN description 'Rule set configured with default_action return'", + "set firewall ipv4 name V4-ACCEPT default-action 'accept'", + "set firewall ipv4 name V4-ACCEPT description 'Rule set configured with default_action accept'", + "set firewall ipv4 name V4-CONTINUE default-action 'continue'", + "set firewall ipv4 name V4-CONTINUE description 'Rule set configured with default_action continue'", + ] + self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_ha.py b/tests/unit/modules/network/vyos/test_vyos_ha.py new file mode 100644 index 00000000..54833a36 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_ha.py @@ -0,0 +1,1405 @@ +# (c) 2021 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/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_ha +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosHaModule(TestVyosModule): + module = vyos_ha + + def setUp(self): + super(TestVyosHaModule, 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.ha.ha.HaFacts.get_config", + ) + + self.execute_show_command = self.mock_execute_show_command.start() + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ha.ha.get_os_version", + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.5" + self.maxDiff = None + + def tearDown(self): + super(TestVyosHaModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + + def load_fixtures(self, commands=None, filename=None): + if filename is None: + filename = "vyos_ha_config.cfg" + + def load_from_file(*args, **kwargs): + output = load_fixture(filename) + return output + + self.execute_show_command.side_effect = load_from_file + + def test_vrrp_merged_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + global_parameters=dict( + garp=dict( + master_refresh=100, + ), + version="3", + ), + groups=[ + dict( + authentication=dict( + password="testpass", + type="plaintext-password", + ), + address=[ + "1.1.1.1", + "3.3.3.3", + "5.5.5.5 interface eth2", + ], + advertise_interval=10, + description="Group_1", + disable=True, + excluded_address=[ + "192.168.1.8", + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh_repeat=100, + master_repeat=3, + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.5", + script="script.sh", + ), + hello_source_address="192.168.1.2", + interface="eth2", + name="g1", + no_preempt=True, + peer_address="192.168.1.3", + priority=100, + rfc3768_compatibility=True, + track=dict( + exclude_vrrp_interface=True, + interface=["eth1"], + ), + transition_script=dict( + backup="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + vrid=20, + ), + ], + snmp="enabled", + sync_groups=[ + dict( + health_check=dict( + failure_count=3, + interval=10, + ), + member=["g1"], + name="sg1", + transition_script=dict( + backup="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrrp_merged(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + name="s1", + address="10.10.10.5", + algorithm="round-robin", + real_server=[ + dict( + address="10.10.50.2", + port=8443, + ), + ], + ), + dict( + name="s2", + address="10.10.10.2", + persistence_timeout=30, + port=81, + protocol="tcp", + ), + dict( + name="s3", + address="10.10.10.3", + port=88, + protocol="udp", + ), + ], + vrrp=dict( + snmp="disabled", + global_parameters=dict( + startup_delay=35, + garp=dict( + master_repeat=6, + ), + ), + groups=[ + dict( + name="g1", + peer_address="192.168.1.3", + priority=100, + disable=False, + no_preempt=False, + ), + dict( + name="g2", + address=[ + "192.168.3.3", + "192.168.4.4 interface eth3", + ], + preempt_delay=15, + ), + ], + sync_groups=[ + dict( + name="sg1", + health_check=dict( + failure_count=5, + ), + ), + ], + ), + ), + state="merged", + ), + ) + commands = [ + "delete high-availability vrrp group g1 no-preempt", + "delete high-availability vrrp group g1 disable", + "delete high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g2 address 192.168.3.3", + "set high-availability vrrp group g2 address 192.168.4.4 interface eth3", + "set high-availability vrrp group g2 preempt-delay 15", + "delete high-availability vrrp snmp", + "set high-availability virtual-server s1 address 10.10.10.5", + "set high-availability virtual-server s1 real-server 10.10.50.2 port 8443", + "set high-availability virtual-server s2 persistence-timeout 30", + "set high-availability virtual-server s2 protocol tcp", + "set high-availability virtual-server s3 address 10.10.10.3", + "set high-availability virtual-server s3 port 88", + "set high-availability virtual-server s3 protocol udp", + "set high-availability vrrp global-parameters garp master-repeat 6", + "set high-availability vrrp global-parameters startup-delay 35", + "set high-availability vrrp sync-group sg1 health-check failure-count 5", + ] + + self.execute_module(changed=True, commands=commands) + + def test_vrrp_replaced(self): + set_module_args( + dict( + config=dict( + disable=False, + virtual_servers=[ + dict( + name="s1", + address="10.10.10.5", + algorithm="round-robin", + real_server=[ + dict( + address="10.10.50.2", + port=8443, + ), + ], + ), + dict( + name="s2", + address="10.10.10.2", + persistence_timeout=30, + port=81, + protocol="tcp", + ), + dict( + name="s3", + address="10.10.10.3", + port=88, + protocol="udp", + ), + ], + vrrp=dict( + snmp="disabled", + global_parameters=dict( + startup_delay=32, + garp=dict( + master_repeat=6, + ), + ), + groups=[ + dict( + name="g1", + peer_address="192.168.1.3", + priority=100, + disable=False, + no_preempt=False, + vrid=20, + ), + dict( + name="g2", + address=[ + "192.168.3.3", + "192.168.4.4 interface eth3", + ], + ), + ], + sync_groups=[ + dict( + name="sg1", + health_check=dict( + failure_count=5, + ), + ), + ], + ), + ), + state="replaced", + ), + ) + commands = [ + "delete high-availability vrrp global-parameters garp master-repeat 5", + "delete high-availability vrrp global-parameters startup-delay 30", + "delete high-availability vrrp group g1 disable", + "delete high-availability vrrp group g1 no-preempt", + "delete high-availability vrrp group g1 rfc3768-compatibility", + "delete high-availability vrrp sync-group sg1 health-check failure-count 3", + "set high-availability vrrp group g2 address 192.168.3.3", + "set high-availability vrrp group g2 address 192.168.4.4 interface eth3", + "delete high-availability disable", + "delete high-availability vrrp snmp", + "set high-availability virtual-server s1 address 10.10.10.5", + "set high-availability virtual-server s1 real-server 10.10.50.2 port 8443", + "set high-availability virtual-server s2 persistence-timeout 30", + "set high-availability virtual-server s2 protocol tcp", + "set high-availability virtual-server s3 address 10.10.10.3", + "set high-availability virtual-server s3 port 88", + "set high-availability virtual-server s3 protocol udp", + "set high-availability vrrp global-parameters garp master-repeat 6", + "set high-availability vrrp global-parameters startup-delay 32", + "set high-availability vrrp sync-group sg1 health-check failure-count 5", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_replaced_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + global_parameters=dict( + garp=dict( + interval=30, + master_delay=10, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=5, + ), + version="3", + startup_delay=30, + ), + groups=[ + dict( + authentication=dict( + password="testpass", + type="plaintext-password", + ), + address=[ + "1.1.1.1", + "3.3.3.3", + "5.5.5.5 interface eth2", + ], + advertise_interval=10, + description="Group_1", + disable=True, + excluded_address=[ + "192.168.1.8", + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.5", + script="script.sh", + ), + hello_source_address="192.168.1.2", + interface="eth2", + name="g1", + no_preempt=True, + peer_address="192.168.1.3", + priority=100, + rfc3768_compatibility=True, + track=dict( + exclude_vrrp_interface=True, + interface=["eth1"], + ), + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + vrid=20, + ), + ], + snmp="enabled", + sync_groups=[ + dict( + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.1", + script="/var/tmp/script.sh", + ), + member=["g1"], + name="sg1", + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrrp_overridden(self): + set_module_args( + dict( + config=dict( + disable=False, + vrrp=dict( + snmp="disabled", + global_parameters=dict( + startup_delay=32, + version=3, + garp=dict( + interval=30, + master_delay=11, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=6, + ), + ), + groups=[ + dict( + name="g1", + description="Group_1", + interface="eth2", + address="1.1.1.1", + disable=True, + no_preempt=True, + rfc3768_compatibility=True, + vrid=20, + peer_address="192.168.1.3", + advertise_interval=10, + priority=100, + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + authentication=dict( + type="plaintext-password", + password="testpass", + ), + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + track=dict( + exclude_vrrp_interface=True, + ), + ), + dict( + name="g2", + address=[ + "192.168.3.3", + "192.168.4.4 interface eth3", + ], + ), + ], + sync_groups=[ + dict( + name="sg1", + member=["g1", "g2"], + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + health_check=dict( + failure_count=4, + interval=10, + ping="192.168.1.100", + script="/var/tmp/script.sh", + ), + ), + ], + ), + virtual_servers=[ + dict( + name="s1", + address="10.10.10.15", + algorithm="round-robin", + delay_loop=60, + fwmark=12, + forward_method="direct", + persistence_timeout=30, + protocol="tcp", + real_server=[ + dict( + address="10.10.10.1", + connection_timeout=61, + port=443, + ), + ], + ), + ], + ), + state="overridden", + ), + ) + commands = [ + "delete high-availability disable", + "delete high-availability virtual-server s1", + "delete high-availability vrrp global-parameters garp", + "delete high-availability vrrp group g1", + "delete high-availability vrrp sync-group sg1", + "set high-availability virtual-server s1 address 10.10.10.15", + "set high-availability virtual-server s1 algorithm round-robin", + "set high-availability virtual-server s1 delay-loop 60", + "set high-availability virtual-server s1 forward-method direct", + "set high-availability virtual-server s1 fwmark 12", + "set high-availability virtual-server s1 persistence-timeout 30", + "set high-availability virtual-server s1 protocol tcp", + "set high-availability virtual-server s1 real-server 10.10.10.1 connection-timeout 61", + "set high-availability virtual-server s1 real-server 10.10.10.1 port 443", + "set high-availability vrrp global-parameters garp interval 30", + "set high-availability vrrp global-parameters garp master-delay 11", + "set high-availability vrrp global-parameters garp master-refresh 100", + "set high-availability vrrp global-parameters garp master-refresh-repeat 200", + "set high-availability vrrp global-parameters garp master-repeat 6", + "set high-availability vrrp global-parameters startup-delay 32", + "set high-availability vrrp group g1 address 1.1.1.1", + "set high-availability vrrp group g1 advertise-interval 10", + "set high-availability vrrp group g1 authentication password testpass", + "set high-availability vrrp group g1 authentication type plaintext-password", + "set high-availability vrrp group g1 description 'Group_1'", + "set high-availability vrrp group g1 disable", + "set high-availability vrrp group g1 garp interval 20", + "set high-availability vrrp group g1 garp master-delay 5", + "set high-availability vrrp group g1 garp master-refresh 50", + "set high-availability vrrp group g1 garp master-refresh-repeat 100", + "set high-availability vrrp group g1 garp master-repeat 3", + "set high-availability vrrp group g1 interface eth2", + "set high-availability vrrp group g1 no-preempt", + "set high-availability vrrp group g1 peer-address 192.168.1.3", + "set high-availability vrrp group g1 priority 100", + "set high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g1 track exclude-vrrp-interface", + "set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh", + "set high-availability vrrp group g1 vrid 20", + "set high-availability vrrp group g2 address 192.168.3.3", + "set high-availability vrrp group g2 address 192.168.4.4 interface eth3", + "set high-availability vrrp sync-group sg1 health-check failure-count 4", + "set high-availability vrrp sync-group sg1 health-check interval 10", + "set high-availability vrrp sync-group sg1 health-check ping 192.168.1.100", + "set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 member g1", + "set high-availability vrrp sync-group sg1 member g2", + "set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_overridden_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + global_parameters=dict( + garp=dict( + interval=30, + master_delay=10, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=5, + ), + version="3", + startup_delay=30, + ), + groups=[ + dict( + authentication=dict( + password="testpass", + type="plaintext-password", + ), + address=[ + "1.1.1.1", + "3.3.3.3", + "5.5.5.5 interface eth2", + ], + advertise_interval=10, + description="Group_1", + disable=True, + excluded_address=[ + "192.168.1.8", + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.5", + script="script.sh", + ), + hello_source_address="192.168.1.2", + interface="eth2", + name="g1", + no_preempt=True, + peer_address="192.168.1.3", + priority=100, + rfc3768_compatibility=True, + track=dict( + exclude_vrrp_interface=True, + interface=["eth1"], + ), + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + vrid=20, + ), + ], + snmp="enabled", + sync_groups=[ + dict( + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.1", + script="/var/tmp/script.sh", + ), + member=["g1"], + name="sg1", + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrrp_rendered(self): + set_module_args( + dict( + config=dict( + virtual_servers=[ + dict( + name="s1", + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + persistence_timeout=30, + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + health_check_script="/var/tmp/script.sh", + port=443, + ), + ], + ), + dict( + name="s2", + address="10.10.10.2", + port=81, + real_server=[ + dict( + address="real1", + connection_timeout=5, + port=8081, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + snmp="enabled", + global_parameters=dict( + startup_delay=31, + version="3", + garp=dict( + interval=30, + master_delay=10, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=5, + ), + ), + groups=[ + dict( + name="g1", + description="Group_1", + interface="eth2", + address="1.1.1.1", + advertise_interval=10, + peer_address="192.168.1.3", + priority=100, + disable=True, + no_preempt=True, + rfc3768_compatibility=True, + vrid=20, + excluded_address=[ + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + authentication=dict( + type="plaintext-password", + password="testpass", + ), + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + track=dict( + exclude_vrrp_interface=True, + interface=[ + "eth0", + ], + ), + ), + dict( + name="g2", + description="Group_2", + interface="eth1", + address=[ + "2.2.2.2", + "2.2.2.3 interface eth3", + ], + disable=False, + no_preempt=False, + rfc3768_compatibility=False, + vrid=11, + health_check=dict( + failure_count=5, + interval=15, + ping="192.168.1.100", + script="/var/tmp/script.sh", + ), + hello_source_address="2.2.2.2", + ), + ], + sync_groups=[ + dict( + name="sg1", + member=[ + "g1", + ], + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.2.100", + script="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="rendered", + ), + ) + rendered_commands = [ + "set high-availability virtual-server s1 address 10.10.10.1", + "set high-availability virtual-server s1 algorithm round-robin", + "set high-availability virtual-server s1 delay-loop 60", + "set high-availability virtual-server s1 forward-method direct", + "set high-availability virtual-server s1 fwmark 10", + "set high-availability virtual-server s1 persistence-timeout 30", + "set high-availability virtual-server s1 protocol tcp", + "set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh", + "set high-availability virtual-server s1 real-server 10.10.50.1 port 443", + "set high-availability virtual-server s2 address 10.10.10.2", + "set high-availability virtual-server s2 port 81", + "set high-availability virtual-server s2 real-server real1 connection-timeout 5", + "set high-availability virtual-server s2 real-server real1 port 8081", + "set high-availability virtual-server s2 real-server real2 port 8080", + "set high-availability vrrp global-parameters garp interval 30", + "set high-availability vrrp global-parameters garp master-delay 10", + "set high-availability vrrp global-parameters garp master-refresh 100", + "set high-availability vrrp global-parameters garp master-refresh-repeat 200", + "set high-availability vrrp global-parameters garp master-repeat 5", + "set high-availability vrrp global-parameters startup-delay 31", + "set high-availability vrrp global-parameters version 3", + "set high-availability vrrp group g1 address 1.1.1.1", + "set high-availability vrrp group g1 advertise-interval 10", + "set high-availability vrrp group g1 authentication password testpass", + "set high-availability vrrp group g1 authentication type plaintext-password", + "set high-availability vrrp group g1 description 'Group_1'", + "set high-availability vrrp group g1 disable", + "set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3", + "set high-availability vrrp group g1 garp interval 20", + "set high-availability vrrp group g1 garp master-delay 5", + "set high-availability vrrp group g1 garp master-refresh 50", + "set high-availability vrrp group g1 garp master-refresh-repeat 100", + "set high-availability vrrp group g1 garp master-repeat 3", + "set high-availability vrrp group g1 interface eth2", + "set high-availability vrrp group g1 no-preempt", + "set high-availability vrrp group g1 peer-address 192.168.1.3", + "set high-availability vrrp group g1 priority 100", + "set high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g1 track exclude-vrrp-interface", + "set high-availability vrrp group g1 track interface eth0", + "set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh", + "set high-availability vrrp group g1 vrid 20", + "set high-availability vrrp group g2 address 2.2.2.2", + "set high-availability vrrp group g2 address 2.2.2.3 interface eth3", + "set high-availability vrrp group g2 description 'Group_2'", + "set high-availability vrrp group g2 health-check failure-count 5", + "set high-availability vrrp group g2 health-check interval 15", + "set high-availability vrrp group g2 health-check ping 192.168.1.100", + "set high-availability vrrp group g2 health-check script /var/tmp/script.sh", + "set high-availability vrrp group g2 hello-source-address 2.2.2.2", + "set high-availability vrrp group g2 interface eth1", + "set high-availability vrrp group g2 vrid 11", + "set high-availability vrrp snmp", + "set high-availability vrrp sync-group sg1 health-check failure-count 3", + "set high-availability vrrp sync-group sg1 health-check interval 10", + "set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100", + "set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 member g1", + "set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh", + ] + result = self.execute_module(changed=False) + # rendered output order is not significant — use sorted comparison + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_commands), + result["rendered"], + ) + + def test_vrrp_parsed(self): + commands = ( + "set high-availability disable", + "set high-availability virtual-server s1 address 10.10.10.1", + "set high-availability virtual-server s1 algorithm round-robin", + "set high-availability virtual-server s1 delay-loop 60", + "set high-availability virtual-server s1 forward-method direct", + "set high-availability virtual-server s1 fwmark 10", + "set high-availability virtual-server s1 persistence-timeout 30", + "set high-availability virtual-server s1 protocol tcp", + "set high-availability virtual-server s1 real-server 10.10.50.1 health-check script '/var/tmp/script.sh'", + "set high-availability virtual-server s1 real-server 10.10.50.1 port 443", + "set high-availability virtual-server s2 address 10.10.10.2", + "set high-availability virtual-server s2 port 81", + "set high-availability virtual-server s2 real-server real1 connection-timeout 5", + "set high-availability virtual-server s2 real-server real1 port 8081", + "set high-availability virtual-server s2 real-server real2 port 8080", + "set high-availability vrrp global-parameters garp interval 30", + "set high-availability vrrp global-parameters garp master-delay 10", + "set high-availability vrrp global-parameters garp master-refresh 100", + "set high-availability vrrp global-parameters garp master-refresh-repeat 200", + "set high-availability vrrp global-parameters garp master-repeat 5", + "set high-availability vrrp global-parameters startup-delay 30", + "set high-availability vrrp global-parameters version 3", + "set high-availability vrrp group g1 address '1.1.1.1'", + "set high-availability vrrp group g1 address '3.3.3.3'", + "set high-availability vrrp group g1 address '5.5.5.5' interface 'eth2'", + "set high-availability vrrp group g1 advertise-interval 10", + "set high-availability vrrp group g1 authentication password 'testpass'", + "set high-availability vrrp group g1 authentication type 'plaintext-password'", + "set high-availability vrrp group g1 description 'Group_1'", + "set high-availability vrrp group g1 disable", + "set high-availability vrrp group g1 excluded-address '192.168.1.7' interface 'eth3'", + "set high-availability vrrp group g1 excluded-address '192.168.1.8'", + "set high-availability vrrp group g1 garp interval 20", + "set high-availability vrrp group g1 garp master-delay 5", + "set high-availability vrrp group g1 garp master-refresh 50", + "set high-availability vrrp group g1 garp master-refresh-repeat 100", + "set high-availability vrrp group g1 garp master-repeat 3", + "set high-availability vrrp group g1 health-check failure-count 3", + "set high-availability vrrp group g1 health-check interval 10", + "set high-availability vrrp group g1 health-check ping '192.168.1.5'", + "set high-availability vrrp group g1 health-check script 'script.sh'", + "set high-availability vrrp group g1 hello-source-address '192.168.1.2'", + "set high-availability vrrp group g1 interface 'eth2'", + "set high-availability vrrp group g1 no-preempt", + "set high-availability vrrp group g1 peer-address '192.168.1.3'", + "set high-availability vrrp group g1 priority 100", + "set high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g1 track exclude-vrrp-interface", + "set high-availability vrrp group g1 track interface 'eth1'", + "set high-availability vrrp group g1 transition-script backup '/var/tmp/script.sh'", + "set high-availability vrrp group g1 transition-script fault '/var/tmp/script.sh'", + "set high-availability vrrp group g1 transition-script master '/var/tmp/script.sh'", + "set high-availability vrrp group g1 transition-script stop '/var/tmp/script.sh'", + "set high-availability vrrp group g1 vrid 20", + "set high-availability vrrp group g2 address '192.168.3.3'", + "set high-availability vrrp group g2 address '192.168.4.4' interface 'eth3'", + "set high-availability vrrp snmp", + "set high-availability vrrp sync-group sg1 health-check failure-count 3", + "set high-availability vrrp sync-group sg1 health-check interval 10", + "set high-availability vrrp sync-group sg1 health-check ping '192.168.1.1'", + "set high-availability vrrp sync-group sg1 health-check script '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 member 'g1'", + "set high-availability vrrp sync-group sg1 transition-script backup '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 transition-script fault '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 transition-script master '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 transition-script stop '/var/tmp/script.sh'", + ) + parsed_str = "\n".join(commands) + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = { + "disable": True, + "virtual_servers": [ + { + "name": "s1", + "address": "10.10.10.1", + "algorithm": "round-robin", + "delay_loop": 60, + "forward_method": "direct", + "fwmark": 10, + "persistence_timeout": 30, + "protocol": "tcp", + "real_server": [ + { + "address": "10.10.50.1", + "health_check_script": "/var/tmp/script.sh", + "port": 443, + }, + ], + }, + { + "name": "s2", + "address": "10.10.10.2", + "port": 81, + "real_server": [ + { + "address": "real1", + "connection_timeout": 5, + "port": 8081, + }, + { + "address": "real2", + "port": 8080, + }, + ], + }, + ], + "vrrp": { + "snmp": "enabled", + "global_parameters": { + "startup_delay": 30, + "version": "3", + "garp": { + "interval": 30, + "master_delay": 10, + "master_refresh": 100, + "master_refresh_repeat": 200, + "master_repeat": 5, + }, + }, + "groups": [ + { + "name": "g1", + "description": "Group_1", + "disable": True, + "no_preempt": True, + "rfc3768_compatibility": True, + "interface": "eth2", + "address": ["1.1.1.1", "3.3.3.3", "5.5.5.5 interface eth2"], + "advertise_interval": 10, + "peer_address": "192.168.1.3", + "priority": 100, + "vrid": 20, + "garp": { + "interval": 20, + "master_delay": 5, + "master_refresh": 50, + "master_refresh_repeat": 100, + "master_repeat": 3, + }, + "authentication": { + "type": "plaintext-password", + "password": "testpass", + }, + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.5", + "script": "script.sh", + }, + "track": { + "exclude_vrrp_interface": True, + "interface": ["eth1"], + }, + "excluded_address": [ + "192.168.1.7 interface eth3", + "192.168.1.8", + ], + "hello_source_address": "192.168.1.2", + }, + { + "name": "g2", + "address": ["192.168.3.3", "192.168.4.4 interface eth3"], + "disable": False, + "no_preempt": False, + "rfc3768_compatibility": False, + }, + ], + "sync_groups": [ + { + "name": "sg1", + "member": ["g1"], + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.1", + "script": "/var/tmp/script.sh", + }, + }, + ], + }, + } + self.assertEqual(parsed_list, result["parsed"]) + + def test_vrrp_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered_list = { + "disable": True, + "virtual_servers": [ + { + "name": "s1", + "address": "10.10.10.1", + "algorithm": "round-robin", + "delay_loop": 60, + "forward_method": "direct", + "fwmark": 10, + "persistence_timeout": 30, + "protocol": "tcp", + "real_server": [ + { + "address": "10.10.50.1", + "health_check_script": "/var/tmp/script.sh", + "port": 443, + }, + ], + }, + { + "name": "s2", + "address": "10.10.10.2", + "port": 81, + "real_server": [ + { + "address": "real1", + "connection_timeout": 5, + "port": 8081, + }, + { + "address": "real2", + "port": 8080, + }, + ], + }, + ], + "vrrp": { + "snmp": "enabled", + "global_parameters": { + "startup_delay": 30, + "version": "3", + "garp": { + "interval": 30, + "master_delay": 10, + "master_refresh": 100, + "master_refresh_repeat": 200, + "master_repeat": 5, + }, + }, + "groups": [ + { + "name": "g1", + "description": "Group_1", + "disable": True, + "no_preempt": True, + "rfc3768_compatibility": True, + "interface": "eth2", + "address": ["1.1.1.1", "3.3.3.3", "5.5.5.5 interface eth2"], + "advertise_interval": 10, + "peer_address": "192.168.1.3", + "priority": 100, + "vrid": 20, + "garp": { + "interval": 20, + "master_delay": 5, + "master_refresh": 50, + "master_refresh_repeat": 100, + "master_repeat": 3, + }, + "authentication": { + "type": "plaintext-password", + "password": "testpass", + }, + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.5", + "script": "script.sh", + }, + "track": { + "exclude_vrrp_interface": True, + "interface": ["eth1"], + }, + "excluded_address": [ + "192.168.1.7 interface eth3", + "192.168.1.8", + ], + "hello_source_address": "192.168.1.2", + }, + ], + "sync_groups": [ + { + "name": "sg1", + "member": ["g1"], + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.1", + "script": "/var/tmp/script.sh", + }, + }, + ], + }, + } + self.assertEqual(gathered_list, result["gathered"]) + + def test_vrrp_groups_deleted(self): + set_module_args( + dict( + config=dict( + vrrp=dict( + groups=[ + dict(name="g1"), + ], + sync_groups=[ + dict(name="sg1"), + ], + ), + ), + state="deleted", + ), + ) + commands = [ + "delete high-availability vrrp group g1", + "delete high-availability vrrp sync-group sg1", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_objs_deleted(self): + set_module_args( + dict( + config=dict( + disable=False, + vrrp=dict( + global_parameters=dict( + startup_delay=32, + garp=dict(), + ), + groups=[ + dict(name="g1"), + ], + sync_groups=[], + ), + virtual_servers=[], + ), + state="deleted", + ), + ) + commands = [ + "delete high-availability virtual-server", + "delete high-availability vrrp global-parameters garp", + "delete high-availability vrrp global-parameters startup-delay", + "delete high-availability vrrp group g1", + "delete high-availability vrrp sync-group", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_deleted_all(self): + set_module_args( + dict( + config=dict(), + state="deleted", + ), + ) + commands = [ + "delete high-availability", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_purged(self): + set_module_args( + dict( + config=dict(), + state="purged", + ), + ) + commands = [ + "delete high-availability", + ] + self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_hostname.py b/tests/unit/modules/network/vyos/test_vyos_hostname.py index a056f6ba..3101cacc 100644 --- a/tests/unit/modules/network/vyos/test_vyos_hostname.py +++ b/tests/unit/modules/network/vyos/test_vyos_hostname.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_interfaces.py index f4a5d2a5..a8f812c1 100644 --- a/tests/unit/modules/network/vyos/test_vyos_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_interfaces.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -236,6 +235,34 @@ class TestVyosInterfacesModule(TestVyosModule): ] self.execute_module(changed=True, commands=commands) + def test_vyos_interfaces_replaced_remove_vrf(self): + # we have a vrf in eth2 at this point, so that should be removed + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict( + name="eth4", + description="Ethernet 4", + enabled=True, + speed="auto", + duplex="auto", + ), + dict(name="eth2", description="Configured by Ansible"), + ], + state="replaced", + ), + ) + + commands = [ + "delete interfaces ethernet eth2 vrf", + "set interfaces ethernet eth2 description 'Configured by Ansible'", + "set interfaces ethernet eth4 description 'Ethernet 4'", + "set interfaces ethernet eth4 duplex 'auto'", + "set interfaces ethernet eth4 speed 'auto'", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_interfaces_merged_enable_vif(self): # merge in enabling vif self.fixture_path = "vyos_interfaces_config_vif.cfg" @@ -363,6 +390,34 @@ class TestVyosInterfacesModule(TestVyosModule): ] self.execute_module(changed=True, commands=commands) + def test_vyos_overridden_remove_vrf(self): + # we have a vrf in eth2 at this point, so that should be removed + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict( + name="eth4", + description="Ethernet 4", + enabled=True, + speed="auto", + duplex="auto", + ), + dict(name="eth2", description="Configured by Ansible"), + ], + state="overridden", + ), + ) + + commands = [ + "set interfaces ethernet eth2 description 'Configured by Ansible'", + "set interfaces ethernet eth4 description 'Ethernet 4'", + "set interfaces ethernet eth4 duplex 'auto'", + "set interfaces ethernet eth4 speed 'auto'", + "delete interfaces ethernet eth2 vrf", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_interfaces_idempotent_disable(self): set_module_args( dict( @@ -416,6 +471,23 @@ class TestVyosInterfacesModule(TestVyosModule): ] self.execute_module(changed=True, commands=commands) + def test_vyos_interfaces_deleted_remove_vrf(self): + # we have a vrf in eth2 at this point, so that should be removed + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict(name="eth2"), + ], + state="deleted", + ), + ) + + commands = [ + "delete interfaces ethernet eth2 vrf", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_interfaces_deleted_remove_all(self): # we have a vif in eth1 at this point, so that should be removed set_module_args( @@ -450,3 +522,115 @@ class TestVyosInterfacesModule(TestVyosModule): ) self.execute_module(failed=True) + + def test_vyos_interfaces_merged_idempotent_vrf(self): + + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict(name="eth2", vrf="green"), + ], + state="merged", + ), + ) + + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_vyos_interfaces_replaced_idempotent_vrf(self): + + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict(name="eth2", vrf="green"), + ], + state="replaced", + ), + ) + + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_vyos_interfaces_overridden_idempotent_vrf(self): + + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict(name="eth2", vrf="green"), + ], + state="overridden", + ), + ) + + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_vyos_interfaces_merged_additional_vrf(self): + + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[ + dict( + name="eth1", + vrf="pink", + ), + ], + state="merged", + ), + ) + + commands = [ + "set interfaces ethernet eth1 vrf 'pink'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_interfaces_gathered_vrf(self): + # gathered facts should surface the vrf set on eth2 + self.fixture_path = "vyos_interfaces_config_vrf.cfg" + set_module_args( + dict( + config=[], + state="gathered", + ), + ) + + result = self.execute_module(changed=False) + eth2_facts = [i for i in result["gathered"] if i["name"] == "eth2"][0] + self.assertEqual(eth2_facts["vrf"], "green") + + def test_vyos_interfaces_parsed_vrf(self): + # parsing a running_config string should surface vrf on eth2 + set_module_args( + dict( + running_config="set interfaces ethernet eth2 vrf 'green'", + state="parsed", + ), + ) + + result = self.execute_module(changed=False) + eth2_facts = [i for i in result["parsed"] if i["name"] == "eth2"][0] + self.assertEqual(eth2_facts["vrf"], "green") + + def test_vyos_interfaces_rendered_vrf(self): + # rendered output should include the vrf set command without touching a device + set_module_args( + dict( + config=[ + dict( + name="eth1", + vrf="pink", + ), + ], + state="rendered", + ), + ) + + result = self.execute_module(changed=False) + commands = [ + "set interfaces ethernet eth1 vrf 'pink'", + ] + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/tests/unit/modules/network/vyos/test_vyos_l3_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_l3_interfaces.py new file mode 100644 index 00000000..7932f63d --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_l3_interfaces.py @@ -0,0 +1,278 @@ +# (c) 2024 VyOS Networks <maintainers@vyos.net> +# +# 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_l3_interfaces +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosL3InterfacesModule(TestVyosModule): + module = vyos_l3_interfaces + + def setUp(self): + super(TestVyosL3InterfacesModule, self).setUp() + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.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() + + def tearDown(self): + super(TestVyosL3InterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None, filename=None): + fixture_data = load_fixture("vyos_l3_interfaces_config.cfg") + self.get_resource_connection_facts.return_value.get_config.return_value = fixture_data + + def test_vyos_l3_interfaces_merged(self): + """Merge a new IPv4 address onto eth1 (currently has 192.0.2.14/24).""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + ipv4=[dict(address="192.0.2.1/24")], + ), + ], + state="merged", + ), + ) + commands = [ + "set interfaces ethernet eth1 address '192.0.2.1/24'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_l3_interfaces_merged_idempotent(self): + """No change when desired config already matches existing config.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + ipv4=[dict(address="192.0.2.14/24")], + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_l3_interfaces_merged_ipv6(self): + """Merge a new IPv6 address onto eth1.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + ipv6=[dict(address="2001:db8::1/32")], + ), + ], + state="merged", + ), + ) + commands = [ + "set interfaces ethernet eth1 address '2001:db8::1/32'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_l3_interfaces_replaced(self): + """Replace eth1 addresses: remove existing, add new.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + ipv4=[dict(address="10.0.0.1/24")], + ), + ], + state="replaced", + ), + ) + commands = [ + "delete interfaces ethernet eth1 address '192.0.2.14/24'", + "set interfaces ethernet eth1 address '10.0.0.1/24'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_l3_interfaces_overridden(self): + """Override: keep only eth2; delete L3 addresses from all other interfaces.""" + set_module_args( + dict( + config=[ + dict( + name="eth2", + ipv4=[dict(address="192.0.2.10/24")], + ipv6=[dict(address="2001:db8::10/32")], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete interfaces ethernet eth0 address 'dhcp'", + "delete interfaces ethernet eth1 address '192.0.2.14/24'", + "delete interfaces ethernet eth3 address '198.51.100.10/24'", + "delete interfaces ethernet eth3 vif 101 address '198.51.100.130/25'", + "delete interfaces ethernet eth3 vif 102 address '2001:db8:4000::3/34'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_l3_interfaces_deleted(self): + """Delete all L3 config from eth1.""" + set_module_args( + dict( + config=[ + dict(name="eth1"), + ], + state="deleted", + ), + ) + commands = [ + "delete interfaces ethernet eth1 address '192.0.2.14/24'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_l3_interfaces_rendered(self): + """Render set commands without connecting to the device.""" + set_module_args( + dict( + config=[ + dict( + name="eth0", + ipv4=[dict(address="dhcp")], + ), + dict( + name="eth1", + ipv4=[dict(address="192.0.2.14/24")], + ), + ], + state="rendered", + ), + ) + rendered_cmds = [ + "set interfaces ethernet eth0 address 'dhcp'", + "set interfaces ethernet eth1 address '192.0.2.14/24'", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_cmds), + result["rendered"], + ) + + def test_vyos_l3_interfaces_gathered(self): + """Gather L3 interface facts from the device.""" + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered = result["gathered"] + # Verify eth1 is present with its IPv4 address + eth1 = next((i for i in gathered if i["name"] == "eth1"), None) + self.assertIsNotNone(eth1) + self.assertIn({"address": "192.0.2.14/24"}, eth1["ipv4"]) + + def test_vyos_l3_interfaces_parsed(self): + """Parse a raw config string into structured data.""" + raw_config = ( + "set interfaces ethernet eth0 address 'dhcp'\n" + "set interfaces ethernet eth1 address '192.0.2.14/24'\n" + ) + set_module_args( + dict( + running_config=raw_config, + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed = result["parsed"] + eth0 = next((i for i in parsed if i["name"] == "eth0"), None) + self.assertIsNotNone(eth0) + self.assertIn({"address": "dhcp"}, eth0["ipv4"]) + eth1 = next((i for i in parsed if i["name"] == "eth1"), None) + self.assertIsNotNone(eth1) + self.assertIn({"address": "192.0.2.14/24"}, eth1["ipv4"]) + + def test_vyos_l3_interfaces_vif_merged(self): + """Merge a new address onto a VIF sub-interface.""" + set_module_args( + dict( + config=[ + dict( + name="eth3", + vifs=[ + dict( + vlan_id=101, + ipv4=[dict(address="198.51.100.200/25")], + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set interfaces ethernet eth3 vif 101 address '198.51.100.200/25'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_l3_interfaces_vif_deleted(self): + """Delete all L3 addresses from eth3, including VIF sub-interfaces.""" + set_module_args( + dict( + config=[ + dict( + name="eth3", + vifs=[ + dict(vlan_id=101), + dict(vlan_id=102), + ], + ), + ], + state="deleted", + ), + ) + commands = [ + "delete interfaces ethernet eth3 vif 101 address '198.51.100.130/25'", + "delete interfaces ethernet eth3 vif 102 address '2001:db8:4000::3/34'", + "delete interfaces ethernet eth3 address '198.51.100.10/24'", + ] + self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_lag_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_lag_interfaces.py index 406ca598..41ba88a4 100644 --- a/tests/unit/modules/network/vyos/test_vyos_lag_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_lag_interfaces.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_lldp_global.py b/tests/unit/modules/network/vyos/test_vyos_lldp_global.py index 0dc1a8d7..ef5233ce 100644 --- a/tests/unit/modules/network/vyos/test_vyos_lldp_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_lldp_global.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_lldp_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_lldp_interfaces.py new file mode 100644 index 00000000..fda8f7f4 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_lldp_interfaces.py @@ -0,0 +1,258 @@ +# (c) 2024 VyOS Networks <maintainers@vyos.net> +# +# 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_lldp_interfaces +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosLldpInterfacesModule(TestVyosModule): + module = vyos_lldp_interfaces + + def setUp(self): + super(TestVyosLldpInterfacesModule, self).setUp() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.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() + + def tearDown(self): + super(TestVyosLldpInterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + + def load_fixtures(self, commands=None, filename=None): + self.get_resource_connection_facts.return_value.get_config.return_value = load_fixture( + "vyos_lldp_interfaces_config.cfg", + ) + + # ------------------------------------------------------------------------- + # merged + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_merged_elin(self): + """Add ELIN location to a new interface (eth3).""" + set_module_args( + dict( + config=[ + dict( + name="eth3", + location=dict(elin="0000000912"), + ), + ], + state="merged", + ), + ) + commands = [ + "set service lldp interface eth3", + "set service lldp interface eth3 location elin '0000000912'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_lldp_interfaces_merged_idempotent(self): + """Existing ELIN config — no change expected.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + location=dict(elin="0000000911"), + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_lldp_interfaces_merged_coordinate(self): + """Add coordinate-based location to a new interface (eth3).""" + set_module_args( + dict( + config=[ + dict( + name="eth3", + location=dict( + coordinate_based=dict( + altitude=1500, + datum="WGS84", + latitude="33.000000N", + longitude="222.000000W", + ), + ), + ), + ], + state="merged", + ), + ) + commands = [ + "set service lldp interface eth3", + "set service lldp interface eth3 location coordinate-based altitude '1500'", + "set service lldp interface eth3 location coordinate-based datum 'WGS84'", + "set service lldp interface eth3 location coordinate-based latitude '33.000000N'", + "set service lldp interface eth3 location coordinate-based longitude '222.000000W'", + ] + self.execute_module(changed=True, commands=commands) + + # ------------------------------------------------------------------------- + # replaced + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_replaced(self): + """Replace eth1 ELIN with a new value.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + location=dict(elin="9999999999"), + ), + ], + state="replaced", + ), + ) + commands = [ + "delete service lldp interface eth1 location", + "set service lldp interface eth1 location elin '9999999999'", + ] + self.execute_module(changed=True, commands=commands) + + # ------------------------------------------------------------------------- + # overridden + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_overridden(self): + """Override: keep eth1 with same ELIN, remove eth2.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + location=dict(elin="0000000911"), + ), + ], + state="overridden", + ), + ) + commands = ["delete service lldp interface eth2"] + self.execute_module(changed=True, commands=commands) + + # ------------------------------------------------------------------------- + # deleted + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_deleted(self): + """Delete LLDP config for eth1.""" + set_module_args( + dict( + config=[dict(name="eth1")], + state="deleted", + ), + ) + commands = ["delete service lldp interface eth1"] + self.execute_module(changed=True, commands=commands) + + # ------------------------------------------------------------------------- + # gathered + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_gathered(self): + """Gather current LLDP interface config from device.""" + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered = result["gathered"] + names = sorted(entry["name"] for entry in gathered) + self.assertEqual(names, ["eth1", "eth2"]) + + eth1 = next(e for e in gathered if e["name"] == "eth1") + self.assertEqual(eth1["location"]["elin"], "0000000911") + + eth2 = next(e for e in gathered if e["name"] == "eth2") + coord = eth2["location"]["coordinate_based"] + self.assertEqual(coord["altitude"], 2200) + self.assertEqual(coord["datum"], "WGS84") + self.assertEqual(coord["latitude"], "33.524449N") + self.assertEqual(coord["longitude"], "222.267255W") + + # ------------------------------------------------------------------------- + # parsed + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_parsed(self): + """Parse a raw config string into structured facts.""" + raw_config = ( + "set service lldp interface eth1 location elin '0000000911'\n" + "set service lldp interface eth2 location coordinate-based altitude '2200'\n" + "set service lldp interface eth2 location coordinate-based datum 'WGS84'\n" + "set service lldp interface eth2 location coordinate-based latitude '33.524449N'\n" + "set service lldp interface eth2 location coordinate-based longitude '222.267255W'\n" + ) + set_module_args(dict(running_config=raw_config, state="parsed")) + result = self.execute_module(changed=False) + parsed = result["parsed"] + names = sorted(entry["name"] for entry in parsed) + self.assertEqual(names, ["eth1", "eth2"]) + + eth1 = next(e for e in parsed if e["name"] == "eth1") + self.assertEqual(eth1["location"]["elin"], "0000000911") + + eth2 = next(e for e in parsed if e["name"] == "eth2") + coord = eth2["location"]["coordinate_based"] + self.assertEqual(coord["altitude"], 2200) + self.assertEqual(coord["datum"], "WGS84") + self.assertEqual(coord["latitude"], "33.524449N") + self.assertEqual(coord["longitude"], "222.267255W") + + # ------------------------------------------------------------------------- + # rendered + # ------------------------------------------------------------------------- + + def test_vyos_lldp_interfaces_rendered(self): + """Render set commands without connecting to the device.""" + set_module_args( + dict( + config=[ + dict( + name="eth1", + location=dict(elin="0000000911"), + ), + ], + state="rendered", + ), + ) + rendered_cmds = [ + "set service lldp interface eth1", + "set service lldp interface eth1 location elin '0000000911'", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_cmds), + ) diff --git a/tests/unit/modules/network/vyos/test_vyos_logging_global.py b/tests/unit/modules/network/vyos/test_vyos_logging_global.py index a6751518..a1ab3c6c 100644 --- a/tests/unit/modules/network/vyos/test_vyos_logging_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_logging_global.py @@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function - __metaclass__ = type from textwrap import dedent @@ -39,11 +38,25 @@ class TestVyosLoggingGlobalModule(TestVyosModule): self.execute_show_command = self.mock_execute_show_command.start() + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.logging_global.logging_global.get_os_version", + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.3" + + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = "1.3" + def tearDown(self): super(TestVyosLoggingGlobalModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + self.mock_facts_get_os_version.stop() def test_vyos_logging_global_merged_idempotent(self): self.execute_show_command.return_value = dedent( @@ -122,7 +135,7 @@ class TestVyosLoggingGlobalModule(TestVyosModule): global_params=dict( archive=dict(size=111, file_num=2), marker_interval=111, - preserve_fqdn="True", + preserve_fqdn=True, facilities=[ dict(facility="cron", severity="debug"), dict(facility="local7", severity="debug"), @@ -194,7 +207,7 @@ class TestVyosLoggingGlobalModule(TestVyosModule): global_params=dict( archive=dict(size=111, file_num=2), marker_interval=111, - preserve_fqdn="True", + preserve_fqdn=True, facilities=[ dict(facility="cron", severity="debug"), dict(facility="local7", severity="debug"), diff --git a/tests/unit/modules/network/vyos/test_vyos_logging_global15.py b/tests/unit/modules/network/vyos/test_vyos_logging_global15.py new file mode 100644 index 00000000..63b3fd31 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_logging_global15.py @@ -0,0 +1,353 @@ +# +# (c) 2021, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from textwrap import dedent +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_logging_global +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule + + +class TestVyosLoggingGlobalModule(TestVyosModule): + module = vyos_logging_global + + def setUp(self): + super(TestVyosLoggingGlobalModule, 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.logging_global.logging_global.Logging_globalFacts.get_logging_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.logging_global.logging_global.get_os_version", + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.5" + + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = "1.5" + + def tearDown(self): + super(TestVyosLoggingGlobalModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + self.mock_facts_get_os_version.stop() + + def test_vyos_logging_global_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + set system syslog console facility all + set system syslog console facility local7 level 'err' + set system syslog console facility news level 'debug' + set system syslog local facility cron level 'debug' + set system syslog local facility local7 level 'debug' + set system syslog marker interval '111' + set system syslog preserve-fqdn + set system syslog remote 10.0.2.12 facility all + set system syslog remote 10.0.2.15 facility all level 'all' + set system syslog remote 10.0.2.15 port '122' + """, + ) + playbook = dict( + config=dict( + console=dict( + facilities=[ + dict(facility="all"), + dict(facility="local7", severity="err"), + dict(facility="news", severity="debug"), + ], + ), + hosts=[ + dict( + hostname="10.0.2.15", + port=122, + facilities=[dict(facility="all", severity="all")], + ), + dict( + hostname="10.0.2.12", + facilities=[dict(facility="all")], + ), + ], + global_params=dict( + marker_interval=111, + preserve_fqdn=True, + facilities=[ + dict(facility="cron", severity="debug"), + dict(facility="local7", severity="debug"), + ], + ), + ), + ) + compare_cmds = [] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module() + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_logging_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + playbook = dict( + config=dict( + console=dict( + facilities=[ + dict(facility="all"), + dict(facility="local7", severity="err"), + dict(facility="news", severity="debug"), + ], + ), + hosts=[ + dict( + hostname="10.0.2.15", + port=122, + facilities=[dict(facility="all", severity="all")], + ), + dict( + hostname="10.0.2.12", + facilities=[dict(facility="all", severity="info")], + ), + ], + global_params=dict( + marker_interval=111, + preserve_fqdn=True, + facilities=[ + dict(facility="cron", severity="debug"), + dict(facility="local7", severity="debug"), + ], + ), + ), + ) + compare_cmds = [ + "set system syslog console facility all", + "set system syslog console facility local7 level err", + "set system syslog console facility news level debug", + "set system syslog local facility cron level debug", + "set system syslog local facility local7 level debug", + "set system syslog marker interval 111", + "set system syslog preserve-fqdn", + "set system syslog remote 10.0.2.15 facility all level all", + "set system syslog remote 10.0.2.15 port 122", + "set system syslog remote 10.0.2.12 facility all level info", + ] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_logging_global_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + set system syslog console facility all + set system syslog console facility local7 level 'err' + set system syslog local facility cron level 'debug' + set system syslog local facility local7 level 'debug' + set system syslog marker interval '111' + set system syslog preserve-fqdn + set system syslog remote 10.0.2.12 facility all + set system syslog remote 10.0.2.15 facility all level 'all' + set system syslog remote 10.0.2.15 port '122' + """, + ) + playbook = dict(config=dict()) + compare_cmds = ["delete system syslog"] + playbook["state"] = "deleted" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_logging_global_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + set system syslog console facility all + set system syslog console facility local7 level 'err' + set system syslog console facility news level 'debug' + set system syslog local facility cron level 'debug' + set system syslog local facility local7 level 'debug' + set system syslog marker interval '111' + set system syslog preserve-fqdn + set system syslog remote 10.0.2.12 facility all + set system syslog remote 10.0.2.15 facility all level 'all' + set system syslog remote 10.0.2.15 port '122' + """, + ) + playbook = dict( + config=dict( + console=dict( + facilities=[dict(facility="local7", severity="emerg")], + ), + hosts=[ + dict( + hostname="10.0.2.15", + port=122, + facilities=[dict(facility="all", severity="all")], + ), + ], + ), + ) + compare_cmds = [ + "delete system syslog console facility all", + "delete system syslog console facility local7", + "delete system syslog console facility news", + "delete system syslog local facility cron", + "delete system syslog local facility local7", + "delete system syslog marker", + "delete system syslog preserve-fqdn", + "delete system syslog remote 10.0.2.12", + "set system syslog console facility local7 level emerg", + ] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_logging_global_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + set system syslog console facility local6 + """, + ) + playbook = dict(config=dict(console=dict(facilities=[dict(facility="local6")]))) + compare_cmds = [] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_logging_global_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + set system syslog console + set system syslog local + """, + ) + playbook = dict( + config=dict( + console=dict( + facilities=[dict(facility="local7", severity="emerg")], + ), + global_params=dict( + facilities=[dict(facility="all", severity="info")], + ), + ), + ) + compare_cmds = [ + "set system syslog console facility local7 level emerg", + "set system syslog local facility all level info", + ] + playbook["state"] = "overridden" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_logging_global_rendered(self): + playbook = dict( + config=dict( + console=dict(facilities=[dict(facility="all")]), + hosts=[ + dict( + hostname="10.0.2.16", + facilities=[dict(facility="local6")], + ), + ], + global_params=dict( + preserve_fqdn=True, + facilities=[dict(facility="all", severity="info")], + ), + ), + ) + compare_cmds = [ + "set system syslog console facility all", + "set system syslog remote 10.0.2.16 facility local6", + "set system syslog preserve-fqdn", + "set system syslog local facility all level info", + ] + playbook["state"] = "rendered" + set_module_args(playbook) + result = self.execute_module() + self.maxDiff = None + self.assertEqual(sorted(result["rendered"]), sorted(compare_cmds)) + + def test_vyos_logging_global_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + set system syslog console facility all + set system syslog local facility all level 'info' + set system syslog remote 10.0.2.1 facility all level 'info' + set system syslog remote 10.0.2.1 port '514' + """, + ), + state="parsed", + ), + ) + parsed = dict( + console=dict(facilities=[dict(facility="all")]), + global_params=dict(facilities=[dict(facility="all", severity="info")]), + hosts=[ + dict( + hostname="10.0.2.1", + port=514, + facilities=[dict(facility="all", severity="info")], + ), + ], + ) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(result["parsed"], parsed) + + def test_vyos_logging_global_gathered(self): + self.execute_show_command.return_value = dedent( + """\ + set system syslog console facility all + set system syslog local facility all level 'info' + set system syslog remote 10.0.2.1 facility all level 'info' + """, + ) + set_module_args(dict(state="gathered")) + gathered = dict( + console=dict(facilities=[dict(facility="all")]), + global_params=dict(facilities=[dict(facility="all", severity="info")]), + hosts=[ + dict( + hostname="10.0.2.1", + facilities=[dict(facility="all", severity="info")], + ), + ], + ) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(result["gathered"], gathered) 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) diff --git a/tests/unit/modules/network/vyos/test_vyos_ntp_global.py b/tests/unit/modules/network/vyos/test_vyos_ntp_global.py index b8adeae7..e14eb43f 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ntp_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_ntp_global.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py index b0a0f0ff..09fcae61 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py index d3f8bc38..af1418db 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py index 2620b1cd..51af858e 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py b/tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py index 5c77cb88..58ceb57b 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv3.py b/tests/unit/modules/network/vyos/test_vyos_ospfv3.py index bdd93612..0fc83b85 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv3.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv3.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_ping.py b/tests/unit/modules/network/vyos/test_vyos_ping.py index 28216962..8d2ce595 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ping.py +++ b/tests/unit/modules/network/vyos/test_vyos_ping.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_prefix_lists.py b/tests/unit/modules/network/vyos/test_vyos_prefix_lists.py index 3da58f72..bd8ade4b 100644 --- a/tests/unit/modules/network/vyos/test_vyos_prefix_lists.py +++ b/tests/unit/modules/network/vyos/test_vyos_prefix_lists.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from textwrap import dedent diff --git a/tests/unit/modules/network/vyos/test_vyos_route_maps.py b/tests/unit/modules/network/vyos/test_vyos_route_maps.py index ce13dcf2..9ff5e073 100644 --- a/tests/unit/modules/network/vyos/test_vyos_route_maps.py +++ b/tests/unit/modules/network/vyos/test_vyos_route_maps.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -50,6 +49,18 @@ class TestVyosRouteMapsModule(TestVyosModule): ) self.execute_show_command = self.mock_execute_show_command.start() + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.route_maps.route_maps.get_os_version", + ) + self.test_version = "1.2" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None def tearDown(self): super(TestVyosRouteMapsModule, self).tearDown() @@ -176,21 +187,25 @@ class TestVyosRouteMapsModule(TestVyosModule): self.execute_module(changed=True, commands=commands) - def test_route_maps_replaced(self): + def test_route_maps_extras_merged(self): set_module_args( dict( config=[ dict( - route_map="test3", + route_map="test2", entries=[ dict( sequence=1, action="permit", + call="2", + continue_sequence=2, match=dict( rpki="invalid", + interface="eth2", metric=1, peer="1.1.1.3", ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + community=dict(community_list="235"), ), set=dict( ipv6_next_hop=dict( @@ -198,9 +213,17 @@ class TestVyosRouteMapsModule(TestVyosModule): value="fdda:5cc1:23:4::1f", ), community=dict(value="internet"), + extcommunity_rt="22:11", + extcommunity_soo="220:110", + extcommunity_bandwidth="100", + extcommunity_bandwidth_non_transitive=True, + atomic_aggregate=True, + aggregator={"ip": "10.20.11.22", "as": "245"}, bgp_extcommunity_rt="22:11", ip_next_hop="10.20.10.22", large_community="10:20:21", + as_path_prepend="100 200 350", + as_path_exclude="150", local_preference=4, metric=5, metric_type="type-2", @@ -209,6 +232,85 @@ class TestVyosRouteMapsModule(TestVyosModule): src="10.0.2.15", tag=4, weight=4, + table=7, + ), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 call 2", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community 10:20:21", + "set policy route-map test2 rule 1 set as-path-prepend '100 200 350'", + "set policy route-map test2 rule 1 set as-path-exclude 150", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set table 7", + "set policy route-map test2 rule 1 set community internet", + "set policy route-map test2 rule 1 set extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set extcommunity-soo 220:110", + "set policy route-map test2 rule 1 set extcommunity bandwidth 100", + "set policy route-map test2 rule 1 set extcommunity bandwidth-non-transitive", + "set policy route-map test2 rule 1 set atomic-aggregate", + "set policy route-map test2 rule 1 set aggregator as 245", + "set policy route-map test2 rule 1 set aggregator ip 10.20.11.22", + "set policy route-map test2 rule 1 match interface eth2", + "set policy route-map test2 rule 1 match metric 1", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 match rpki invalid", + "set policy route-map test2 rule 1 match community community-list 235", + "set policy route-map test2 rule 1 continue 2", + ] + + self.execute_module(changed=True, commands=commands) + + def test_route_maps_replaced(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="100:100"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="fdda:5cc1:23:4::12", + tag=4, + weight=4, ), ), ], @@ -220,10 +322,12 @@ class TestVyosRouteMapsModule(TestVyosModule): commands = [ "delete policy route-map test3 rule 1 match interface eth2", "set policy route-map test3 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test3 rule 1 set community 100:100", "set policy route-map test3 rule 1 set large-community 10:20:21", "set policy route-map test3 rule 1 set metric-type type-2", "set policy route-map test3 rule 1 set originator-id 10.0.2.2", "set policy route-map test3 rule 1 set tag 4", + "set policy route-map test3 rule 1 set src fdda:5cc1:23:4::12", "set policy route-map test3 rule 1 match peer 1.1.1.3", ] self.execute_module(changed=True, commands=commands) @@ -328,6 +432,32 @@ class TestVyosRouteMapsModule(TestVyosModule): ] self.execute_module(changed=True, commands=commands) + def test_route_maps__deny_overridden(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="deny", + match=dict(rpki="invalid", peer="1.1.1.5"), + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete policy route-map test3", + "set policy route-map test2 rule 1 action deny", + "set policy route-map test2 rule 1 match peer 1.1.1.5", + "set policy route-map test2 rule 1 match rpki invalid", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_route_maps_overridden_idempotent(self): set_module_args( dict( diff --git a/tests/unit/modules/network/vyos/test_vyos_route_maps14.py b/tests/unit/modules/network/vyos/test_vyos_route_maps14.py new file mode 100644 index 00000000..b60ad44a --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_route_maps14.py @@ -0,0 +1,716 @@ +# (c) 2021 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/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_route_maps +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosRouteMapsModule(TestVyosModule): + module = vyos_route_maps + + def setUp(self): + super(TestVyosRouteMapsModule, 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.route_maps.route_maps.Route_mapsFacts.get_config", + ) + + self.execute_show_command = self.mock_execute_show_command.start() + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.route_maps.route_maps.get_os_version", + ) + self.test_version = "1.4" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None + + def tearDown(self): + super(TestVyosRouteMapsModule, 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_route_maps_config_v14.cfg" + + def load_from_file(*args, **kwargs): + output = load_fixture(filename) + return output + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_route_maps_merged_idempotent(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_route_maps_merged(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="10.0.2.15", + tag=4, + weight=4, + ), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community replace 10:20:21", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set community replace internet", + "set policy route-map test2 rule 1 match interface eth2", + "set policy route-map test2 rule 1 match metric 1", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 match rpki invalid", + ] + + self.execute_module(changed=True, commands=commands) + + def test_route_maps_extras_merged(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="permit", + call="2", + continue_sequence=2, + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + community=dict(community_list="235"), + protocol="bgp", + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + extcommunity_rt="22:11", + extcommunity_soo="220:110", + extcommunity_bandwidth="100", + extcommunity_bandwidth_non_transitive=True, + atomic_aggregate=True, + aggregator={"ip": "10.20.11.22", "as": "245"}, + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + as_path_prepend="100 200 350", + as_path_exclude="150", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="10.0.2.15", + tag=4, + weight=4, + table=7, + ), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 call 2", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community replace 10:20:21", + "set policy route-map test2 rule 1 set as-path prepend '100 200 350'", + "set policy route-map test2 rule 1 set as-path exclude 150", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set table 7", + "set policy route-map test2 rule 1 set community replace internet", + "set policy route-map test2 rule 1 set extcommunity rt 22:11", + "set policy route-map test2 rule 1 set extcommunity soo 220:110", + "set policy route-map test2 rule 1 set extcommunity bandwidth 100", + "set policy route-map test2 rule 1 set extcommunity bandwidth-non-transitive", + "set policy route-map test2 rule 1 set atomic-aggregate", + "set policy route-map test2 rule 1 set aggregator as 245", + "set policy route-map test2 rule 1 set aggregator ip 10.20.11.22", + "set policy route-map test2 rule 1 match interface eth2", + "set policy route-map test2 rule 1 match metric 1", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 match rpki invalid", + "set policy route-map test2 rule 1 match protocol bgp", + "set policy route-map test2 rule 1 match community community-list 235", + "set policy route-map test2 rule 1 continue 2", + ] + + self.execute_module(changed=True, commands=commands) + + def test_route_maps_replaced(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="100:100"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="fdda:5cc1:23:4::12", + tag=4, + weight=4, + ), + ), + ], + ), + ], + state="replaced", + ), + ) + commands = [ + "delete policy route-map test3 rule 1 match interface eth2", + "set policy route-map test3 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test3 rule 1 set community replace 100:100", + "set policy route-map test3 rule 1 set large-community replace 10:20:21", + "set policy route-map test3 rule 1 set metric-type type-2", + "set policy route-map test3 rule 1 set originator-id 10.0.2.2", + "set policy route-map test3 rule 1 set tag 4", + "set policy route-map test3 rule 1 set src fdda:5cc1:23:4::12", + "set policy route-map test3 rule 1 match peer 1.1.1.3", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_route_maps_replaced_idempotent(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_route_maps_overridden(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="permit", + match=dict(rpki="invalid", peer="1.1.1.3"), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="10.0.2.15", + tag=4, + weight=4, + ), + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete policy route-map test3", + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community replace 10:20:21", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set community replace internet", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match rpki invalid", + ] + self.execute_module(changed=True, commands=commands) + + def test_route_maps__deny_overridden(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="deny", + match=dict(rpki="invalid", peer="1.1.1.5"), + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete policy route-map test3", + "set policy route-map test2 rule 1 action deny", + "set policy route-map test2 rule 1 match peer 1.1.1.5", + "set policy route-map test2 rule 1 match rpki invalid", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_route_maps_overridden_idempotent(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_route_maps_rendered(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + dict( + route_map="test1", + entries=[ + dict( + sequence=1, + action="permit", + description="test", + on_match=dict(next=True), + ), + dict( + sequence=2, + action="permit", + on_match=dict(goto=4), + ), + ], + ), + ], + state="rendered", + ), + ) + rendered_cmds = [ + "set policy route-map test3 rule 1 action permit", + "set policy route-map test3 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test3 rule 1 set ip-next-hop 10.20.10.20", + "set policy route-map test3 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test3 rule 1 set local-preference 4", + "set policy route-map test3 rule 1 set metric 5", + "set policy route-map test3 rule 1 set metric-type type-1", + "set policy route-map test3 rule 1 set origin egp", + "set policy route-map test3 rule 1 set originator-id 10.0.2.3", + "set policy route-map test3 rule 1 set src 10.0.2.15", + "set policy route-map test3 rule 1 set tag 5", + "set policy route-map test3 rule 1 set weight 4", + "set policy route-map test3 rule 1 set community replace internet", + "set policy route-map test3 rule 1 match interface eth2", + "set policy route-map test3 rule 1 match metric 1", + "set policy route-map test3 rule 1 match peer 1.1.1.2", + "set policy route-map test3 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test3 rule 1 match rpki invalid", + "set policy route-map test1 rule 1 description test", + "set policy route-map test1 rule 1 action permit", + "set policy route-map test1 rule 1 on-match next", + "set policy route-map test1 rule 2 action permit", + "set policy route-map test1 rule 2 on-match goto 4", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_cmds), + result["rendered"], + ) + + def test_yos_route_maps_parsed(self): + parsed_str = ( + "set policy route-map test3 rule 1 action 'permit'" + "\nset policy route-map test3 rule 1 match interface 'eth2'\nset policy route-map test3 rule 1 match ipv6 nexthop" + " 'fdda:5cc1:23:4::1f'\nset policy route-map test3 rule 1 match metric '1'\nset policy route-map test3 rule 1 match peer " + "'1.1.1.2'\nset policy route-map test3 rule 1 match rpki 'invalid'\nset policy route-map test3 rule 1 set bgp-extcommunity-rt " + "'22:11'\nset policy route-map test3 rule 1 set community replace 'internet'\nset policy route-map test3 rule 1 set ipv6-next-hop global" + " 'fdda:5cc1:23:4::1f'\nset policy route-map test3 rule 1 set ip-next-hop '10.20.10.20'\nset policy route-map " + "test3 rule 1 set local-preference '4'\nset policy route-map test3 rule 1 set metric '5'\nset policy route-map test3 " + "rule 1 set metric-type 'type-1'\nset policy route-map test3 rule 1 set origin 'egp'\nset policy route-map test3 rule 1 set originator-id " + "'10.0.2.3'\nset policy route-map test3 rule 1 set src '10.0.2.15'" + "\nset policy route-map test3 rule 1 set tag '5'\nset policy route-map test3 rule 1 set weight '4'" + ) + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = [ + { + "entries": [ + { + "action": "permit", + "match": { + "interface": "eth2", + "ipv6": {"next_hop": "fdda:5cc1:23:4::1f"}, + "metric": 1, + "peer": "1.1.1.2", + "rpki": "invalid", + }, + "sequence": 1, + "set": { + "bgp_extcommunity_rt": "22:11", + "community": {"value": "internet"}, + "ip_next_hop": "10.20.10.20", + "ipv6_next_hop": { + "ip_type": "global", + "value": "fdda:5cc1:23:4::1f", + }, + "local_preference": "4", + "metric": "5", + "metric_type": "type-1", + "origin": "egp", + "originator_id": "10.0.2.3", + "src": "10.0.2.15", + "tag": "5", + "weight": "4", + }, + }, + ], + "route_map": "test3", + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_vyos_route_maps_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered_list = [ + { + "entries": [ + { + "action": "permit", + "match": { + "interface": "eth2", + "ipv6": {"next_hop": "fdda:5cc1:23:4::1f"}, + "metric": 1, + "peer": "1.1.1.2", + "rpki": "invalid", + }, + "sequence": 1, + "set": { + "bgp_extcommunity_rt": "22:11", + "community": {"value": "internet"}, + "ip_next_hop": "10.20.10.20", + "ipv6_next_hop": { + "ip_type": "global", + "value": "fdda:5cc1:23:4::1f", + }, + "local_preference": "4", + "metric": "5", + "metric_type": "type-1", + "origin": "egp", + "originator_id": "10.0.2.3", + "src": "10.0.2.15", + "tag": "5", + "weight": "4", + }, + }, + ], + "route_map": "test3", + }, + ] + self.assertEqual(gathered_list, result["gathered"]) + + def test_vyos_route_maps_deleted(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + ), + set=dict( + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="deleted", + ), + ) + commands = ["delete policy route-map test3"] + self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_snmp_server.py b/tests/unit/modules/network/vyos/test_vyos_snmp_server.py index 9ac650bf..91abb69f 100644 --- a/tests/unit/modules/network/vyos/test_vyos_snmp_server.py +++ b/tests/unit/modules/network/vyos/test_vyos_snmp_server.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_static_routes.py b/tests/unit/modules/network/vyos/test_vyos_static_routes.py index 71f1fbf1..955c997a 100644 --- a/tests/unit/modules/network/vyos/test_vyos_static_routes.py +++ b/tests/unit/modules/network/vyos/test_vyos_static_routes.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_static_routes14.py b/tests/unit/modules/network/vyos/test_vyos_static_routes14.py index e5e40823..2dd4c0aa 100644 --- a/tests/unit/modules/network/vyos/test_vyos_static_routes14.py +++ b/tests/unit/modules/network/vyos/test_vyos_static_routes14.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch diff --git a/tests/unit/modules/network/vyos/test_vyos_system.py b/tests/unit/modules/network/vyos/test_vyos_system.py index cf405cab..56874cba 100644 --- a/tests/unit/modules/network/vyos/test_vyos_system.py +++ b/tests/unit/modules/network/vyos/test_vyos_system.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch @@ -45,11 +44,26 @@ class TestVyosSystemModule(TestVyosModule): ) self.load_config = self.mock_load_config.start() + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version", + ) + self.test_version = "1.2" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None + def tearDown(self): super(TestVyosSystemModule, self).tearDown() self.mock_get_config.stop() self.mock_load_config.stop() + self.mock_get_os_version.stop() + self.mock_facts_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): self.get_config.return_value = load_fixture("vyos_config_config.cfg") @@ -112,3 +126,67 @@ class TestVyosSystemModule(TestVyosModule): "delete system name-server", ] self.execute_module(changed=True, commands=commands) + + +class TestVyosSystemModule14(TestVyosModule): + module = vyos_system + + def setUp(self): + super(TestVyosSystemModule14, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_system.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version", + ) + self.test_version = "1.4" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None + + def tearDown(self): + super(TestVyosSystemModule14, self).tearDown() + + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_get_os_version.stop() + self.mock_facts_get_os_version.stop() + + def load_fixtures(self, commands=None, filename=None): + self.get_config.return_value = load_fixture("vyos_config_config.cfg") + + def test_vyos_system_domain_search(self): + set_module_args(dict(domain_search=["foo.example.com", "bar.example.com"])) + commands = [ + "set system domain-search 'foo.example.com'", + "set system domain-search 'bar.example.com'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_system_clear_domain_search(self): + set_module_args(dict(domain_search=[])) + commands = ["delete system domain-search"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_system_clear_all(self): + set_module_args(dict(state="absent")) + commands = [ + "delete system host-name", + "delete system domain-search", + "delete system domain-name", + "delete system name-server", + ] + self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_user.py b/tests/unit/modules/network/vyos/test_vyos_user.py index e8c50783..27ab1f38 100644 --- a/tests/unit/modules/network/vyos/test_vyos_user.py +++ b/tests/unit/modules/network/vyos/test_vyos_user.py @@ -62,6 +62,36 @@ class TestVyosUserModule(TestVyosModule): ["set system login user ansible authentication plaintext-password test"], ) + def test_vyos_user_password_special_chars(self): + set_module_args(dict(name="ansible", configured_password="test$123!@")) + result = self.execute_module(changed=True) + self.assertEqual( + result["commands"], + [ + "set system login user ansible authentication plaintext-password 'test$123!@'", + ], + ) + + def test_vyos_user_password_embedded_quote(self): + set_module_args(dict(name="ansible", configured_password="pa'ss")) + result = self.execute_module(changed=True) + self.assertEqual( + result["commands"], + [ + "set system login user ansible authentication plaintext-password 'pa'\"'\"'ss'", + ], + ) + + def test_vyos_user_password_complex_special_chars(self): + set_module_args(dict(name="ansible", configured_password="P@ss w0rd!$#'xy\\")) + result = self.execute_module(changed=True) + self.assertEqual( + result["commands"], + [ + "set system login user ansible authentication plaintext-password 'P@ss w0rd!$#'\"'\"'xy\\'", + ], + ) + def test_vyos_user_delete(self): set_module_args(dict(name="ansible", state="absent")) result = self.execute_module(changed=True) @@ -141,6 +171,39 @@ class TestVyosUserModule(TestVyosModule): ], ) + def test_vyos_user_set_security_keys(self): + set_module_args( + dict( + name="ansible", + public_keys=[ + dict( + name="ecdsa@host", + key="AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTY", + type="sk-ecdsa-sha2-nistp256@openssh.com", + ), + dict( + name="ed25519@host", + key="AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIFIR0jrMvBdmvTJNY5EDhOD", + type="sk-ssh-ed25519@openssh.com", + ), + ], + ), + ) + result = self.execute_module(changed=True) + self.assertEqual( + result["commands"], + [ + "set system login user ansible authentication public-keys " + "ecdsa@host key 'AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTY'", + "set system login user ansible authentication public-keys ecdsa@host " + "type 'sk-ecdsa-sha2-nistp256@openssh.com'", + "set system login user ansible authentication public-keys ed25519@host " + "key 'AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIFIR0jrMvBdmvTJNY5EDhOD'", + "set system login user ansible authentication public-keys ed25519@host " + "type 'sk-ssh-ed25519@openssh.com'", + ], + ) + def test_vyos_user_set_ssh_key_idempotent(self): set_module_args( dict( @@ -237,3 +300,43 @@ class TestVyosUserModule(TestVyosModule): ), ) result = self.execute_module(changed=False) + + def test_vyos_user_aggregate_with_public_keys(self): + set_module_args( + dict( + aggregate=[ + dict( + name="user1", + public_keys=[ + dict( + name="user1@host1", + key="AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2u", + type="ssh-ed25519", + ), + ], + ), + dict( + name="user2", + public_keys=[ + dict( + name="user2@host2", + key="AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2u", + type="ssh-ed25519", + ), + ], + ), + ], + ), + ) + result = self.execute_module(changed=True) + self.assertEqual( + sorted(result["commands"]), + sorted( + [ + "set system login user user1 authentication public-keys user1@host1 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2u'", + "set system login user user1 authentication public-keys user1@host1 type 'ssh-ed25519'", + "set system login user user2 authentication public-keys user2@host2 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2u'", + "set system login user user2 authentication public-keys user2@host2 type 'ssh-ed25519'", + ], + ), + ) diff --git a/tests/unit/modules/network/vyos/test_vyos_vlan.py b/tests/unit/modules/network/vyos/test_vyos_vlan.py new file mode 100644 index 00000000..5bbf87c2 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vlan.py @@ -0,0 +1,158 @@ +# (c) 2016 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/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_vlan +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosVlanModule(TestVyosModule): + module = vyos_vlan + + def setUp(self): + super(TestVyosVlanModule, self).setUp() + + self.mock_load_config = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_vlan.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_run_commands = patch( + "ansible_collections.vyos.vyos.plugins.modules.vyos_vlan.run_commands", + ) + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestVyosVlanModule, self).tearDown() + self.mock_load_config.stop() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None, filename=None): + self.load_config.return_value = dict(diff=None, session="session") + if filename == "empty": + self.run_commands.return_value = [ + load_fixture("vyos_vlan_show_interfaces_empty.cfg"), + ] + else: + self.run_commands.return_value = [ + load_fixture("vyos_vlan_show_interfaces.cfg"), + ] + + def test_vyos_vlan_present(self): + """Create a new VLAN with a description on eth2 (not in have).""" + set_module_args( + dict( + vlan_id=300, + name="vlan-300", + interfaces=["eth2"], + state="present", + ) + ) + commands = ["set interfaces ethernet eth2 vif 300 description vlan-300"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_vlan_present_no_change(self): + """Existing VLAN 100 on eth0 — no commands should be generated.""" + set_module_args( + dict( + vlan_id=100, + name="vlan-100", + interfaces=["eth0"], + state="present", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_vlan_absent(self): + """Delete an existing VLAN (200 on eth1).""" + set_module_args( + dict( + vlan_id=200, + interfaces=["eth1"], + state="absent", + ) + ) + commands = ["delete interfaces ethernet eth1 vif 200"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_vlan_absent_no_change(self): + """Delete a VLAN that does not exist — no commands.""" + set_module_args( + dict( + vlan_id=999, + interfaces=["eth0"], + state="absent", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_vlan_aggregate(self): + """Create two new VLANs via aggregate; neither is in have.""" + set_module_args( + dict( + aggregate=[ + dict(vlan_id=301, interfaces=["eth2"], name="vlan-301"), + dict(vlan_id=302, interfaces=["eth2"], name="vlan-302"), + ], + ) + ) + commands = [ + "set interfaces ethernet eth2 vif 301 description vlan-301", + "set interfaces ethernet eth2 vif 302 description vlan-302", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_vlan_purge(self): + """Purge VLANs not in want. Want only VLAN 100; VLAN 200 should be removed. + + The fixed parser only maps ethX.Y sub-interfaces, so bare ethX interfaces + (vlan_id=None) are not in have. Only real VLANs (eth1.200) are purged. + """ + set_module_args( + dict( + vlan_id=100, + interfaces=["eth0"], + state="present", + purge=True, + ) + ) + commands = [ + "delete interfaces ethernet eth1 vif 200", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_vlan_with_address(self): + """Create a VLAN with an IP address and no description.""" + set_module_args( + dict( + vlan_id=400, + address="10.10.40.1/24", + interfaces=["eth1"], + state="present", + ) + ) + commands = ["set interfaces ethernet eth1 vif 400 address 10.10.40.1/24"] + self.execute_module(changed=True, commands=commands, filename="empty") diff --git a/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py new file mode 100644 index 00000000..6c616ad1 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec.py @@ -0,0 +1,568 @@ +# +# (c) 2026, Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from textwrap import dedent +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_vpn_ipsec +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosVpnIpsecModule(TestVyosModule): + module = vyos_vpn_ipsec + + def setUp(self): + super(TestVyosVpnIpsecModule, 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.vpn_ipsec.vpn_ipsec.Vpn_ipsecFacts.get_vpn_ipsec_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestVyosVpnIpsecModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + # Single fixture file, reused across every test. Carries: two + # ike_group entries (IKE-TEST richly configured incl. + # dead_peer_detection/disable_mobike, IKE-EXTRA minimal, to prove + # "unlisted items preserved"); esp_group with compression+proposal; + # psk/ppk with multi-value id lists; a profile; and every top-level + # singleton field (disable_uniqreqids, interface, log, options). + def load_fixtures(self, commands=None, filename=None): + if filename == "EMPTY": + self.execute_show_command.side_effect = None + self.execute_show_command.return_value = "" + return + + if filename is None: + filename = "vyos_vpn_ipsec_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_vpn_ipsec_merged_idempotent(self): + playbook = dict( + config=dict( + esp_group=[ + dict( + name="ESP-TEST", + compression=True, + proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")], + ), + ], + ike_group=[ + dict( + name="IKE-TEST", + key_exchange="ikev2", + disable_mobike=True, + dead_peer_detection=dict(action="restart", interval=15, timeout=60), + proposal=[ + dict(proposal_id=1, encryption="aes256", hash="sha256", dh_group=14), + ], + ), + ], + ), + state="merged", + ) + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), []) + + def test_vyos_vpn_ipsec_merged_new_group_leaves_existing_untouched(self): + playbook = dict( + config=dict( + esp_group=[ + dict( + name="ESP-NEW", + proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")], + ), + ], + ), + state="merged", + ) + compare_cmds = [ + "set vpn ipsec esp-group ESP-NEW", + "set vpn ipsec esp-group ESP-NEW proposal 1", + "set vpn ipsec esp-group ESP-NEW proposal 1 encryption aes256", + "set vpn ipsec esp-group ESP-NEW proposal 1 hash sha256", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_merged_bool_field_toggle(self): + # disable_mobike explicitly set to True in want when have already + # has it True -> no-op; here we flip a different bool + # (compression on ESP-TEST is already True in have) by instead + # adding a brand new bool-bearing field: options.virtual_ip, + # entirely absent from have. + playbook = dict(config=dict(options=dict(virtual_ip=True)), state="merged") + compare_cmds = ["set vpn ipsec options virtual-ip"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_merged_add_psk_id(self): + # multi-value list field: add one id, existing ids must survive + playbook = dict( + config=dict( + authentication=dict( + psk=[dict(name="PSK-TEST", id=["third@example.com"])], + ), + ), + state="merged", + ) + compare_cmds = ["set vpn ipsec authentication psk PSK-TEST id third@example.com"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_merged_create_ppk(self): + playbook = dict( + config=dict( + authentication=dict( + ppk=[dict(name="PPK-NEW", id=["new-ppk-id"], secret="new-secret")], + ), + ), + state="merged", + ) + compare_cmds = [ + "set vpn ipsec authentication ppk PPK-NEW", + "set vpn ipsec authentication ppk PPK-NEW id new-ppk-id", + "set vpn ipsec authentication ppk PPK-NEW secret 'new-secret'", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_merged_profile_add_bind_tunnel(self): + # multi-value list field on an EXISTING profile: add one tunnel, + # existing tun0 must survive + playbook = dict( + config=dict( + profile=[dict(name="testprofile", bind_tunnel=["tun1"])], + ), + state="merged", + ) + compare_cmds = ["set vpn ipsec profile testprofile bind tunnel tun1"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_merged_create_profile_from_scratch(self): + # Regression test: creating a brand new profile must correctly + # emit its authentication.mode/pre_shared_secret commands. This + # specific path was silently broken (a leftover dotted-Jinja + # setval caused render() to return nothing, so addcmd() quietly + # dropped both commands with no error) -- caught only by a real + # device integration test, since no existing unit test actually + # created a profile from scratch or touched its authentication + # sub-dict through generate_commands(). + playbook = dict( + config=dict( + profile=[ + dict( + name="NEWPROFILE", + authentication=dict( + mode="pre-shared-secret", + pre_shared_secret="brand-new-secret", + ), + esp_group="ESP-TEST", + ike_group="IKE-TEST", + ), + ], + ), + state="merged", + ) + compare_cmds = [ + "set vpn ipsec profile NEWPROFILE", + "set vpn ipsec profile NEWPROFILE authentication mode pre-shared-secret", + "set vpn ipsec profile NEWPROFILE authentication pre-shared-secret 'brand-new-secret'", + "set vpn ipsec profile NEWPROFILE esp-group ESP-TEST", + "set vpn ipsec profile NEWPROFILE ike-group IKE-TEST", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # replaced + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_replaced_only_named_item_touched(self): + playbook = dict( + config=dict( + esp_group=[ + dict( + name="ESP-TEST", + proposal=[dict(proposal_id=1, encryption="aes128", hash="sha256")], + ), + ], + ), + state="replaced", + ) + compare_cmds = [ + # compression omitted from want -> reconciled away (attribute + # inside the selected instance is always reset under replaced) + "delete vpn ipsec esp-group ESP-TEST compression", + "set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128", + # ike_group/profile/psk/ppk not named at all -> left alone. + # Top-level singleton fields (no instance scope) are always + # reconciled under replaced/overridden regardless of what's + # named elsewhere. + "delete vpn ipsec disable-uniqreqids", + "delete vpn ipsec interface eth0", + "delete vpn ipsec interface eth1", + "delete vpn ipsec log level 1", + "delete vpn ipsec log subsystem chd", + "delete vpn ipsec log subsystem ike", + "delete vpn ipsec options flexvpn", + "delete vpn ipsec options retransmission attempts 3", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_replaced_idempotent(self): + # want fully mirrors the ENTIRE fixture state for every top-level + # singleton field plus the one named esp_group -- proves + # replaced's per-item/per-field reconciliation doesn't spuriously + # touch a fully-matching configuration. + playbook = dict( + config=dict( + esp_group=[ + dict( + name="ESP-TEST", + compression=True, + proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")], + ), + ], + disable_uniqreqids=True, + interface=["eth0", "eth1"], + log=dict(level=1, subsystem=["chd", "ike"]), + options=dict(flexvpn=True, retransmission=dict(attempts=3)), + ), + state="replaced", + ) + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), []) + + def test_vyos_vpn_ipsec_replaced_scoped_dead_peer_detection(self): + # naming ike_group under replaced without dead_peer_detection -> + # every DPD field reconciled away (nested-dict attribute reset) + playbook = dict( + config=dict( + ike_group=[dict(name="IKE-TEST", key_exchange="ikev2")], + ), + state="replaced", + ) + compare_cmds = [ + "delete vpn ipsec ike-group IKE-TEST dead-peer-detection action restart", + "delete vpn ipsec ike-group IKE-TEST dead-peer-detection interval 15", + "delete vpn ipsec ike-group IKE-TEST dead-peer-detection timeout 60", + "delete vpn ipsec ike-group IKE-TEST disable-mobike", + "delete vpn ipsec ike-group IKE-TEST proposal 1", + "delete vpn ipsec disable-uniqreqids", + "delete vpn ipsec interface eth0", + "delete vpn ipsec interface eth1", + "delete vpn ipsec log level 1", + "delete vpn ipsec log subsystem chd", + "delete vpn ipsec log subsystem ike", + "delete vpn ipsec options flexvpn", + "delete vpn ipsec options retransmission attempts 3", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # overridden + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_overridden_removes_unlisted_instances(self): + playbook = dict( + config=dict( + esp_group=[ + dict( + name="ESP-TEST", + proposal=[dict(proposal_id=1, encryption="aes128", hash="sha256")], + ), + ], + ), + state="overridden", + ) + compare_cmds = [ + "delete vpn ipsec esp-group ESP-TEST compression", + "set vpn ipsec esp-group ESP-TEST proposal 1 encryption aes128", + "delete vpn ipsec ike-group IKE-TEST", + "delete vpn ipsec ike-group IKE-EXTRA", + "delete vpn ipsec profile testprofile", + "delete vpn ipsec authentication psk PSK-TEST", + "delete vpn ipsec authentication ppk PPK-TEST", + "delete vpn ipsec disable-uniqreqids", + "delete vpn ipsec interface eth0", + "delete vpn ipsec interface eth1", + "delete vpn ipsec log level 1", + "delete vpn ipsec log subsystem chd", + "delete vpn ipsec log subsystem ike", + "delete vpn ipsec options flexvpn", + "delete vpn ipsec options retransmission attempts 3", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # deleted -- bare (delete everything) + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_deleted_bare(self): + playbook = dict(state="deleted") + compare_cmds = [ + "delete vpn ipsec esp-group ESP-TEST", + "delete vpn ipsec ike-group IKE-TEST", + "delete vpn ipsec ike-group IKE-EXTRA", + "delete vpn ipsec profile testprofile", + "delete vpn ipsec authentication psk PSK-TEST", + "delete vpn ipsec authentication ppk PPK-TEST", + "delete vpn ipsec disable-uniqreqids", + "delete vpn ipsec interface eth0", + "delete vpn ipsec interface eth1", + "delete vpn ipsec log level 1", + "delete vpn ipsec log subsystem chd", + "delete vpn ipsec log subsystem ike", + "delete vpn ipsec options flexvpn", + "delete vpn ipsec options retransmission attempts 3", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_deleted_bare_idempotent(self): + playbook = dict(state="deleted") + set_module_args(playbook) + result = self.execute_module(changed=False, filename="EMPTY") + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), []) + + # ------------------------------------------------------------------- + # deleted -- scoped (delete only what's named) + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_deleted_scoped_named_item_only(self): + playbook = dict(config=dict(ike_group=[dict(name="IKE-EXTRA")]), state="deleted") + compare_cmds = ["delete vpn ipsec ike-group IKE-EXTRA"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_deleted_scoped_nonexistent_name_idempotent(self): + playbook = dict(config=dict(ike_group=[dict(name="IKE-NONEXISTENT")]), state="deleted") + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), []) + + def test_vyos_vpn_ipsec_deleted_scoped_interface_element(self): + # element-level list deletion: name only eth0, eth1 must survive + playbook = dict(config=dict(interface=["eth0"]), state="deleted") + compare_cmds = ["delete vpn ipsec interface eth0"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_deleted_scoped_log_subsystem_element(self): + playbook = dict(config=dict(log=dict(subsystem=["chd"])), state="deleted") + compare_cmds = ["delete vpn ipsec log subsystem chd"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_deleted_scoped_options_field(self): + playbook = dict(config=dict(options=dict(flexvpn=True)), state="deleted") + compare_cmds = ["delete vpn ipsec options flexvpn"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # rendered (no device contact -- have is empty regardless of fixture) + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_rendered(self): + playbook = dict( + config=dict( + esp_group=[ + dict( + name="ESP-RENDER-TEST", + proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")], + ), + ], + ), + state="rendered", + ) + compare_cmds = [ + "set vpn ipsec esp-group ESP-RENDER-TEST", + "set vpn ipsec esp-group ESP-RENDER-TEST proposal 1", + "set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 encryption aes256", + "set vpn ipsec esp-group ESP-RENDER-TEST proposal 1 hash sha256", + ] + set_module_args(playbook) + result = self.execute_module() + self.maxDiff = None + self.assertEqual(sorted(result["rendered"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # parsed (reads running_config directly, not the mocked show command) + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 encryption aes256 + set vpn ipsec esp-group ESP-PARSE-TEST proposal 1 hash sha256 + set vpn ipsec ike-group IKE-PARSE-TEST key-exchange ikev2 + """, + ), + state="parsed", + ), + ) + parsed = dict( + esp_group=[ + dict( + name="ESP-PARSE-TEST", + proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")], + ), + ], + ike_group=[dict(name="IKE-PARSE-TEST", key_exchange="ikev2")], + ) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(result["parsed"], parsed) + + # ------------------------------------------------------------------- + # gathered + # ------------------------------------------------------------------- + + def _normalize_scalar_lists(self, node): + """Multi-value scalar-list fields (id, subsystem, interface, ...) + have no meaningful order -- dict_merge's internal list handling + doesn't guarantee a stable sequence between runs. Sort them + in-place (recursively) before comparing so tests aren't + sensitive to that non-determinism, while list-of-dict + collections (already sorted by name in process_facts) are left + untouched. + """ + if isinstance(node, dict): + for k, v in node.items(): + if isinstance(v, list) and v and all(not isinstance(i, dict) for i in v): + node[k] = sorted(v) + else: + self._normalize_scalar_lists(v) + elif isinstance(node, list): + for item in node: + self._normalize_scalar_lists(item) + return node + + def test_vyos_vpn_ipsec_gathered(self): + set_module_args(dict(state="gathered")) + gathered = dict( + authentication=dict( + ppk=[dict(name="PPK-TEST", id=["ppk-id-1"], secret="test-ppk-secret")], + psk=[ + dict( + name="PSK-TEST", + id=["local@example.com", "remote@example.com"], + secret="test-not-real-secret", + ), + ], + ), + disable_uniqreqids=True, + esp_group=[ + dict( + name="ESP-TEST", + compression=True, + proposal=[dict(proposal_id=1, encryption="aes256", hash="sha256")], + ), + ], + ike_group=[ + dict(name="IKE-EXTRA", key_exchange="ikev1"), + dict( + name="IKE-TEST", + key_exchange="ikev2", + disable_mobike=True, + dead_peer_detection=dict(action="restart", interval=15, timeout=60), + proposal=[ + dict(proposal_id=1, encryption="aes256", hash="sha256", dh_group=14), + ], + ), + ], + interface=["eth0", "eth1"], + log=dict(level=1, subsystem=["chd", "ike"]), + options=dict(flexvpn=True, retransmission=dict(attempts=3)), + profile=[ + dict( + name="testprofile", + authentication=dict( + mode="pre-shared-secret", + pre_shared_secret="test-not-real-secret", + ), + bind_tunnel=["tun0"], + esp_group="ESP-TEST", + ike_group="IKE-TEST", + ), + ], + ) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual( + self._normalize_scalar_lists(result["gathered"]), + self._normalize_scalar_lists(gathered), + ) diff --git a/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py new file mode 100644 index 00000000..014fb867 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vpn_ipsec_s2s.py @@ -0,0 +1,245 @@ +# +# (c) 2026, Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_vpn_ipsec_s2s +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosVpnIpsecS2sModule(TestVyosModule): + module = vyos_vpn_ipsec_s2s + + def setUp(self): + super(TestVyosVpnIpsecS2sModule, 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.vpn_ipsec_s2s.vpn_ipsec_s2s.Vpn_ipsec_s2sFacts.get_vpn_ipsec_s2s_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestVyosVpnIpsecS2sModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + # Single fixture file, reused across every test. Carries PEER-TEST + # (richly configured -- authentication, tunnel, vti, every scalar + # type) and PEER-EXTRA (minimal, to prove "unlisted peer preserved + # under replaced / removed under overridden"). + def load_fixtures(self, commands=None, filename=None): + if filename is None: + filename = "vyos_vpn_ipsec_s2s_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_vpn_ipsec_s2s_merged_idempotent(self): + playbook = dict( + config=dict( + peer=[ + dict( + name="PEER-TEST", + authentication=dict( + local_id="local@example.com", + mode="pre-shared-secret", + remote_id="remote@example.com", + ), + childless="prefer", + connection_type="initiate", + default_esp_group="ESP-TEST", + description="test peer for site-to-site module", + force_udp_encapsulation=True, + ike_group="IKE-TEST", + ikev2_reauth="inherit", + local_address="any", + remote_address=["203.0.113.1"], + replay_window=32, + virtual_address=["0.0.0.0"], + tunnel=[ + dict( + tunnel_id=1, + esp_group="ESP-TEST", + protocol="tcp", + priority=10, + local=dict(port=443, prefix=["10.0.0.0/24"]), + remote=dict(port=443, prefix=["10.1.0.0/24"]), + ), + ], + vti=dict( + bind="vti0", + esp_group="ESP-TEST", + traffic_selector=dict( + local=dict(prefix=["10.2.0.0/24"]), + remote=dict(prefix=["10.3.0.0/24"]), + ), + ), + ), + ], + ), + state="merged", + ) + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), []) + + def test_vyos_vpn_ipsec_s2s_merged_new_peer_leaves_existing_untouched(self): + playbook = dict( + config=dict(peer=[dict(name="PEER-NEW", ike_group="IKE-TEST")]), + state="merged", + ) + compare_cmds = [ + "set vpn ipsec site-to-site peer PEER-NEW", + "set vpn ipsec site-to-site peer PEER-NEW ike-group 'IKE-TEST'", + ] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # replaced + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_s2s_replaced_only_named_peer_touched(self): + # Name PEER-TEST fully (mirroring the fixture exactly) except a + # changed tunnel priority -- PEER-EXTRA must be left alone. + playbook = dict( + config=dict( + peer=[ + dict( + name="PEER-TEST", + authentication=dict( + local_id="local@example.com", + mode="pre-shared-secret", + remote_id="remote@example.com", + ), + childless="prefer", + connection_type="initiate", + default_esp_group="ESP-TEST", + description="test peer for site-to-site module", + force_udp_encapsulation=True, + ike_group="IKE-TEST", + ikev2_reauth="inherit", + local_address="any", + remote_address=["203.0.113.1"], + replay_window=32, + virtual_address=["0.0.0.0"], + tunnel=[ + dict( + tunnel_id=1, + esp_group="ESP-TEST", + protocol="tcp", + priority=99, + local=dict(port=443, prefix=["10.0.0.0/24"]), + remote=dict(port=443, prefix=["10.1.0.0/24"]), + ), + ], + vti=dict( + bind="vti0", + esp_group="ESP-TEST", + traffic_selector=dict( + local=dict(prefix=["10.2.0.0/24"]), + remote=dict(prefix=["10.3.0.0/24"]), + ), + ), + ), + ], + ), + state="replaced", + ) + compare_cmds = ["set vpn ipsec site-to-site peer PEER-TEST tunnel 1 priority '99'"] + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) + + # ------------------------------------------------------------------- + # overridden + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_s2s_overridden_removes_unlisted_peer(self): + playbook = dict( + config=dict(peer=[dict(name="PEER-TEST", ike_group="IKE-TEST")]), + state="overridden", + ) + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertIn("delete vpn ipsec site-to-site peer PEER-EXTRA", result["commands"]) + # PEER-TEST had far more fields than just ike_group in the + # fixture -- overridden must reconcile all of those away too, + # since only ike_group was named. + self.assertIn( + "delete vpn ipsec site-to-site peer PEER-TEST default-esp-group 'ESP-TEST'", + result["commands"], + ) + + # ------------------------------------------------------------------- + # deleted -- bare and scoped + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_s2s_deleted_bare(self): + set_module_args(dict(state="deleted")) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertIn("delete vpn ipsec site-to-site peer PEER-TEST", result["commands"]) + self.assertIn("delete vpn ipsec site-to-site peer PEER-EXTRA", result["commands"]) + + def test_vyos_vpn_ipsec_s2s_deleted_scoped_named_peer_only(self): + set_module_args(dict(config=dict(peer=[dict(name="PEER-EXTRA")]), state="deleted")) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], ["delete vpn ipsec site-to-site peer PEER-EXTRA"]) + + # ------------------------------------------------------------------- + # rendered / parsed / gathered + # ------------------------------------------------------------------- + + def test_vyos_vpn_ipsec_s2s_rendered(self): + playbook = dict( + config=dict(peer=[dict(name="PEER-RENDER-TEST", ike_group="IKE-TEST")]), + state="rendered", + ) + compare_cmds = [ + "set vpn ipsec site-to-site peer PEER-RENDER-TEST", + "set vpn ipsec site-to-site peer PEER-RENDER-TEST ike-group 'IKE-TEST'", + ] + set_module_args(playbook) + result = self.execute_module() + self.maxDiff = None + self.assertEqual(sorted(result["rendered"]), sorted(compare_cmds)) + + def test_vyos_vpn_ipsec_s2s_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + self.maxDiff = None + names = sorted(p["name"] for p in result["gathered"]["peer"]) + self.assertEqual(names, ["PEER-EXTRA", "PEER-TEST"]) diff --git a/tests/unit/modules/network/vyos/test_vyos_vrf.py b/tests/unit/modules/network/vyos/test_vyos_vrf.py new file mode 100644 index 00000000..aa6284a0 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vrf.py @@ -0,0 +1,688 @@ +# (c) 2021 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 MagicMock, patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_vrf +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosVrfModule(TestVyosModule): + module = vyos_vrf + + def setUp(self): + super(TestVyosVrfModule, self).setUp() + + self.fake_connection = MagicMock() + self.fake_connection.get.return_value = "{}" + + self.fake_connection.get_device_info.return_value = { + "version": "1.5", + } + + self.fake_connection.get_device_info.return_value = { + "network_os_major_version": "1.5", + } + patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos.get_connection", + return_value=self.fake_connection, + ).start() + + def _get_resource_connection_side_effect(module, *args, **kwargs): + module._connection = self.fake_connection + return self.fake_connection + + patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", + side_effect=_get_resource_connection_side_effect, + ).start() + + patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", + side_effect=_get_resource_connection_side_effect, + ).start() + + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vrf.vrf.VrfFacts.get_config", + ).start() + + patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vrf.vrf.get_os_version", + return_value="1.5", + ).start() + + patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global.get_os_version", + return_value="1.5", + ).start() + + self.maxDiff = None + + def tearDown(self): + super(TestVyosVrfModule, self).tearDown() + patch.stopall() + + def load_fixtures(self, commands=None, filename=None): + if filename is None: + filename = "vyos_vrf_config.cfg" + + def load_from_file(*args, **kwargs): + return load_fixture(filename) + + self.mock_execute_show_command.side_effect = load_from_file + + def test_vrf_merged_idempotent(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=False, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=True, + ), + ], + protocols=dict( + bgp=dict( + as_number=65000, + neighbor=[ + dict( + address="192.0.2.1", + remote_as=65002, + ), + dict( + address="1.1.1.3", + remote_as=400, + passive=True, + ), + ], + ), + ), + ), + ], + ), + state="merged", + ), + ) + + self.execute_module(changed=False, commands=[]) + + def test_vrf_merged(self): + set_module_args( + dict( + config=dict( + bind_to_all=False, + instances=[ + dict( + name="vrf-green", + description="green-vrf", + table_id=110, + vni=1010, + protocols=dict( + ospf=dict( + log_adjacency_changes="detail", + max_metric=dict( + router_lsa=dict( + administrative=True, + on_shutdown=10, + on_startup=10, + ), + ), + default_information=dict( + originate=dict( + always=True, + metric=10, + metric_type=2, + ), + ), + auto_cost=dict( + reference_bandwidth=2, + ), + neighbor=[ + dict( + neighbor_id="192.0.11.12", + poll_interval=10, + priority=2, + ), + ], + redistribute=[ + dict( + route_type="bgp", + metric=10, + metric_type=2, + ), + ], + parameters=dict( + router_id="192.0.1.1", + rfc1583_compatibility=True, + abr_type="cisco", + ), + areas=[ + dict( + area_id="2", + area_type=dict( + normal=True, + ), + authentication="plaintext-password", + shortcut="enable", + ), + dict( + area_id="3", + area_type=dict( + nssa=dict( + set=True, + ), + ), + ), + dict( + area_id="4", + area_type=dict( + stub=dict( + default_cost=20, + ), + ), + network=[ + dict( + address="192.0.2.0/24", + ), + ], + range=[ + dict( + address="192.0.3.0/24", + cost=10, + ), + dict( + address="192.0.4.0/24", + cost=12, + ), + ], + ), + ], + ), + ), + ), + ], + ), + state="merged", + ), + ) + + commands = [ + "delete vrf bind-to-all", + "set vrf name vrf-green table 110", + "set vrf name vrf-green vni 1010", + "set vrf name vrf-green description green-vrf", + "set vrf name vrf-green protocols ospf log-adjacency-changes 'detail'", + "set vrf name vrf-green protocols ospf max-metric router-lsa administrative", + "set vrf name vrf-green protocols ospf max-metric router-lsa on-shutdown 10", + "set vrf name vrf-green protocols ospf max-metric router-lsa on-startup 10", + "set vrf name vrf-green protocols ospf default-information originate always", + "set vrf name vrf-green protocols ospf default-information originate metric 10", + "set vrf name vrf-green protocols ospf default-information originate metric-type 2", + "set vrf name vrf-green protocols ospf auto-cost reference-bandwidth '2'", + "set vrf name vrf-green protocols ospf neighbor 192.0.11.12", + "set vrf name vrf-green protocols ospf neighbor 192.0.11.12 poll-interval 10", + "set vrf name vrf-green protocols ospf neighbor 192.0.11.12 priority 2", + "set vrf name vrf-green protocols ospf redistribute bgp", + "set vrf name vrf-green protocols ospf redistribute bgp metric 10", + "set vrf name vrf-green protocols ospf redistribute bgp metric-type 2", + "set vrf name vrf-green protocols ospf parameters router-id '192.0.1.1'", + "set vrf name vrf-green protocols ospf parameters rfc1583-compatibility", + "set vrf name vrf-green protocols ospf parameters abr-type 'cisco'", + "set vrf name vrf-green protocols ospf area '2'", + "set vrf name vrf-green protocols ospf area 2 area-type normal", + "set vrf name vrf-green protocols ospf area 2 authentication plaintext-password", + "set vrf name vrf-green protocols ospf area 2 shortcut enable", + "set vrf name vrf-green protocols ospf area '3'", + "set vrf name vrf-green protocols ospf area 3 area-type nssa", + "set vrf name vrf-green protocols ospf area '4'", + "set vrf name vrf-green protocols ospf area 4 area-type stub default-cost 20", + "set vrf name vrf-green protocols ospf area 4 network 192.0.2.0/24", + "set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24", + "set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 cost 10", + "set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24", + "set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 cost 12", + ] + + self.execute_module(changed=True, commands=commands) + + def test_vrf_replaced(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf-replaced", + disable=True, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=False, + route_maps=[ + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=False, + ), + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "set vrf name vrf-blue description blue-vrf-replaced", + "set vrf name vrf-blue disable", + "delete vrf name vrf-red ip disable-forwarding", + "delete vrf name vrf-red ipv6 nht no-resolve-via-default", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrf_replaced_idempotent(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=False, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=True, + ), + ], + ), + ], + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrf_overridden(self): + set_module_args( + dict( + config=dict( + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=True, + table_id=103, + vni=1002, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + protocols=dict( + bgp=dict( + as_number=65000, + neighbor=[ + dict( + address="192.0.2.1", + remote_as=65003, + ), + ], + ), + ), + ), + ], + ), + state="overridden", + ), + ) + commands = [ + "delete vrf name vrf-blue", + "commit", + "delete vrf name vrf-red", + "commit", + "set vrf name vrf-blue table 103", + "set vrf name vrf-blue vni 1002", + "set vrf name vrf-blue description blue-vrf", + "set vrf name vrf-blue disable", + "set vrf name vrf-red table 101", + "set vrf name vrf-red vni 1001", + "set vrf name vrf-red description red-vrf", + "set vrf name vrf-red disable", + "set vrf name vrf-red protocols bgp system-as 65000", + "set vrf name vrf-red protocols bgp neighbor 192.0.2.1 remote-as 65003", + "delete vrf bind-to-all", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrf_overridden_idempotent(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=False, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + nht_no_resolve_via_default=False, + route_maps=[ + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + disable_forwarding=False, + nht_no_resolve_via_default=True, + ), + ], + protocols=dict( + bgp=dict( + as_number=65000, + neighbor=[ + dict( + address="192.0.2.1", + remote_as=65002, + ), + dict( + address="1.1.1.3", + remote_as=400, + passive=True, + ), + ], + ), + ), + ), + ], + ), + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrf_rendered(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-green", + description="green-vrf", + disabled=True, + table_id=105, + vni=1000, + ), + dict( + name="vrf-amber", + description="amber-vrf", + disable=False, + table_id=111, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="ospf"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=False, + ), + ], + ), + ], + ), + state="rendered", + ), + ) + rendered_commands = [ + "set vrf bind-to-all", + "set vrf name vrf-green table 105", + "set vrf name vrf-green vni 1000", + "set vrf name vrf-green description green-vrf", + "set vrf name vrf-green disable", + "set vrf name vrf-amber table 111", + "set vrf name vrf-amber vni 1001", + "set vrf name vrf-amber description amber-vrf", + "set vrf name vrf-amber ip protocol ospf route-map rm1", + "set vrf name vrf-amber ip disable-forwarding", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_commands), + result["rendered"], + ) + + def test_vrf_parsed(self): + commands = ( + "set vrf bind-to-all", + "set vrf name vrf1 description 'red'", + "set vrf name vrf1 disable", + "set vrf name vrf1 table 101", + "set vrf name vrf1 vni 501", + "set vrf name vrf2 description 'green'", + "set vrf name vrf2 disable", + "set vrf name vrf2 table 102", + "set vrf name vrf2 vni 102", + "set vrf name vrf1 ip disable-forwarding", + "set vrf name vrf1 ip nht no-resolve-via-default", + "set vrf name vrf-red ip protocol ospf route-map 'rm1'", + "set vrf name vrf-red ipv6 nht no-resolve-via-default", + ) + parsed_str = "\n".join(commands) + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = { + "bind_to_all": True, + "instances": [ + { + "address_family": [ + { + "afi": "ipv4", + "disable_forwarding": True, + "nht_no_resolve_via_default": True, + }, + ], + "description": "red", + "disable": True, + "name": "vrf1", + }, + { + "description": "green", + "disable": True, + "name": "vrf2", + }, + { + "address_family": [ + { + "afi": "ipv4", + "disable_forwarding": False, + "nht_no_resolve_via_default": False, + "route_maps": [ + { + "protocol": "ospf", + "rm_name": "rm1", + }, + ], + }, + { + "afi": "ipv6", + "disable_forwarding": False, + "nht_no_resolve_via_default": True, + }, + ], + "disable": False, + "name": "vrf-red", + }, + ], + } + self.assertEqual(parsed_list, result["parsed"]) + + def test_vrf_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered_list = { + "bind_to_all": True, + "instances": [ + { + "description": "blue-vrf", + "disable": False, + "name": "vrf-blue", + "table_id": 100, + "vni": 1000, + }, + { + "address_family": [ + { + "afi": "ipv4", + "disable_forwarding": True, + "nht_no_resolve_via_default": False, + "route_maps": [ + {"protocol": "rip", "rm_name": "rm1"}, + ], + }, + { + "afi": "ipv6", + "disable_forwarding": False, + "nht_no_resolve_via_default": True, + }, + ], + "description": "red-vrf", + "disable": True, + "name": "vrf-red", + "protocols": { + "bgp": { + "as_number": 65000, + "neighbor": [ + {"address": "1.1.1.3", "passive": True, "remote_as": 400}, + {"address": "192.0.2.1", "remote_as": 65002}, + ], + }, + }, + "table_id": 101, + "vni": 1001, + }, + ], + } + self.assertEqual(gathered_list, result["gathered"]) + + def test_vrf_deleted(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + ), + ], + ), + state="deleted", + ), + ) + commands = [ + "delete vrf bind-to-all", + "delete vrf name vrf-blue", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrf_all_deleted(self): + set_module_args( + dict( + state="deleted", + ), + ) + commands = [ + "delete vrf", + ] + self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/vyos_module.py b/tests/unit/modules/network/vyos/vyos_module.py index 60d94b5a..b1bdc2ec 100644 --- a/tests/unit/modules/network/vyos/vyos_module.py +++ b/tests/unit/modules/network/vyos/vyos_module.py @@ -18,7 +18,6 @@ # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type import json @@ -30,7 +29,6 @@ from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( ModuleTestCase, ) - fixture_path = os.path.join(os.path.dirname(__file__), "fixtures") fixture_data = {} diff --git a/tests/unit/modules/utils.py b/tests/unit/modules/utils.py index e2d17abd..c2a19070 100644 --- a/tests/unit/modules/utils.py +++ b/tests/unit/modules/utils.py @@ -1,6 +1,5 @@ from __future__ import absolute_import, division, print_function - __metaclass__ = type import json import unittest |
