Automate Leaf and Spine Deployment - Part2

input variable validation

8 February 2021   4 min read

The 2nd post in the ‘Automate Leaf and Spine Deployment’ series describes process used for validating the variable files format and content. The idea behind this offline pre-validation is to catch any errors in the variable files before device configuration is attempted. Fail fast based on logic instead of failing halfway through a build. It wont catch everything but will eliminate a lot of the needless errors that would break a fabric build.


The variable validation is done with ‘Python assert’ in a filter_plugin and the result returned to the ‘Ansible assert’ module as either a pass phrase (xxx.yml unittest pass) or a list of the problems that then triggers Ansible assert to fail.

All the error messages returned by pre-validation start with the nested location of the variable to make it easier to find. For example svc_intf.intf.single_homed.ip_vlan is a singled homed interfaces ip_vlan variable within the service_interface.yml variable file.

Don’t repeat yourself, don’t repeat yourself, don’t repeat yourself, err…..

There is a trend of commonly used assert validations so to eliminate replicating the same code the majority of assertions are separate methods. This reduces the complexity and makes it easier to add new validations by keeping it DRY.

  • assert_regex_search: Matches a specified regex pattern anywhere within the string
  • assert_regex_match: Matches a specified regex pattern at the beginning of the string
  • assert_equal: Asserts a variable does match the specified value
  • assert_equal_less: Asserts a variable is equal to or less than the specified value
  • assert_equal_more: Asserts a variable is equal to or more than the specified value
  • assert_not_equal: Asserts a variable does not match the specified value
  • assert_in: Asserts a variable is within the specified value
  • assert_not_in: Asserts a variable is not within the specified value
  • assert_integer: Asserts a variable is an integer (number)
  • assert_string: Asserts a variable is a string
  • assert_list: Asserts a variable is a list
  • assert_list_len: Asserts that the variable is a list with x number of elements
  • assert_boolean: Asserts a variable is boolean True or False
  • assert_ipv4: Asserts that it is a valid IP address or network address (correct mask and within it). Takes addresses with no subnet mask
  • assert_ipv4_and_mask: Same as assert_ipv4 but also ensures it has a subnet mask (for interfaces and prefix-lists)
  • assert_exist: Asserts a dictionary exists
  • duplicate_in_list: Asserts there are no duplicate elements in a list, if so returns the duplicates in the error message
  • check_used_intfs: Asserts whether there are enough free interfaces to accommodate all the defined interfaces
  • check_used_fbc_intfs: Asserts whether defined interfaces or loopbacks are duplicated/ overlap
  • asset_pfx_lst: Asserts that the prefix-list is in the entry correct format (le/ge 0-32), prefix is a valid IP address and not duplicated

This doesn’t cover absolutely everything, there are some situations with nested dictionaries where it is not possible to use a pre-defined method due to the multiple layers of validation.

Structure

The filter_plugin input_validate.py is split into methods for each variable file with each Ansible pre-task following the same structure of per-variable file. These pre-tasks are conditional and only run if that variable file has been defined under var_files.

    - name: "Validate the contents of the variable files"
      block:
      - name: "PRE_VAL >> Validating the contents of base.yml"
        assert:
          that: "{{ bse.device_name | input_bse_validate(bse.addr, bse.users) }} == 'base.yml unittest pass'"
          fail_msg: "{{ bse.device_name | input_bse_validate(bse.addr, bse.users) }}"
        when: bse is defined
      run_once: true
      tags: [pre_val]

The information block at the start of the inventory plugin defines which variables are being checked with a brief description of what it is being validated for. The size of input_validate.py got a bit out of control (1500+ lines) so the variable path is also included in the actual validation within the script making it a bit easier to find a validation when searching within input_validate.py.

-base configuration variables using base.yml:
bse.device_name: Ensures that the device names used match the correct format as that is heavily used in inventory script logic
bse.addr: Ensures that the network addresses entered are valid networks (or IP for loopback) with the correct subnet mask
bse.users: Ensures that username and password is present

-core fabric configuration variables using fabric.yml:
fbc.network_size: Ensures the number of each type of device is within the limits and constraints
fbc.num_intf: Ensures is one number, then a comma and then up to 3
fbc.route.authentication: Ensure that the BGP and OSPF contains no whitespace

Running pre-validation

pre-validation is run using the pre_val tag and conditionally only checks variable files that have been defined under var_files. It can be run using the inventory plugin but will fail if any of the values used to create the inventory (from base.yml and fabric.yml) are wrong. Therefore it is better idea to run it against a dummy hosts file, nothing has to be reachable in hosts as it is being run locally against variable files rather than devices.

ansible-playbook PB_build_fabric.yml -i hosts --tag post_val
ansible-playbook PB_build_fabric.yml -i inv_from_vars_cfg.yml --tag post_val