From c3f8a97de9f0bfffa26c9fa6cf7c914ecc3a3978 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Sun, 3 Apr 2022 12:02:41 +0000 Subject: [PATCH] Move docs into their own directory --- README.md | 200 ++++--------------------------------------- docs/config-guide.md | 3 + docs/design.md | 175 +++++++++++++++++++++++++++++++++++++ docs/user-guide.md | 11 +++ 4 files changed, 207 insertions(+), 182 deletions(-) create mode 100644 docs/config-guide.md create mode 100644 docs/design.md create mode 100644 docs/user-guide.md diff --git a/README.md b/README.md index f67effb..b1c5590 100644 --- a/README.md +++ b/README.md @@ -23,198 +23,34 @@ sudo pip3 install pyinstaller ## Build the tool pyinstaller vppcfg --onefile +dist/vppcfg -h ``` ## Running ``` -dist/vppcfg -h -usage: vppcfg [-h] [-s SCHEMA] [-d] [-q] [-f] -c CONFIG {check,plan,apply} ... +usage: vppcfg [-h] [-d] [-q] [-f] {check,dump,plan,apply} ... positional arguments: - {check,plan,apply} + {check,dump,plan,apply} + check check given YAML config for validity (no VPP) + dump dump current running VPP configuration (VPP readonly) + plan plan changes from current VPP dataplane to target config (VPP readonly) + apply apply changes from current VPP dataplane to target config optional arguments: -h, --help show this help message and exit - -s SCHEMA, --schema SCHEMA - YAML schema validation file - -d, --debug Enable debug logging, default False - -q, --quiet Be quiet (only warnings/errors), default False - -f, --force Force progress despite warnings, default False - -c CONFIG, --config CONFIG - YAML configuration file for vppcfg + -d, --debug enable debug logging, default False + -q, --quiet be quiet (only warnings/errors), default False + -f, --force force progress despite warnings, default False + +Please see vppcfg -h for per-command arguments ``` -## Design +## Documentation -### YAML Configuration - -The main file that is handled by this program is the **Configuration File**. - -## Validation - -There are three types of validation: _schema_ which ensures that the input YAML has the correct -fields of well known types, _semantic_ which ensures that the configuration doesn't violate -semantic constraints and _runtime_ which ensures that the configuration can be applied to the -VPP daemon. - -### Schema Validators - -First the configuration file is held against a structural validator, provided by [Yamale](https://github.com/23andMe/Yamale/). -Based on a validation schema in `schema.yaml`, the input file is checked for syntax correctness. -For example, a `dot1q` field must be an integer between 1 and 4095, wile an `lcp` string must -match a certain regular expression. After this first pass of syntax validation, I'm certain that -_if_ a field is set, it is of the right type (ie. string, int, enum). - -### Semantic Validators - -A set of semantic validators, each with a unique name, ensure that the _semantics_ of the YAML -are correct. For example, a physical interface cannot have an LCP, addresses or sub-interfaces, -if it is to be a member of a BondEthernet. - -Validators are expected to return a tuple of (bool,[string]) where the boolean signals success -(False meaning the validator rejected the configuration file, True meaning it is known to be -correct), and a list of zero or more strings which contain messages meant for human consumption. - -### Runtime Validators - -After the configuration file is considered syntax and semanticly valid, there is one more set of -checks to perform -- runtime validators ensure that the configuration elements such as physical -network devices (ie. `HundredGigabitEthernet12/0/0` or plugin `lcpng` are present on the system. -It does this by connecting to VPP and querying the runtime state to ensure that what is modeled -in the configuration file is able to be committed. - -## Unit Tests - -It is incredibly important that changes to this codebase, particularly the validators, are well -tested. Unit tests are provided in the `unittests/` directory with a Python test runner in -`tests.py`. Besides regular unittests provided by the Python framework, a YAMLTest is a test which -reads a two-document YAML file, with the first document describing test metadata, and the second -document being a candidate configuration to test, and it then runs all schema and semantic -validators and reports back. - -The format of the YAMLTest is as follows: -``` -test: - description: str() - errors: - expected: list(str()) - count: int() ---- - -``` - -Fields: -* ***description***: A string describing the behavior that is being tested against. Any failure - of the unittest will print this description in the error logs. -* ***errors.expected***: A list of regular expressions, that will be expected to be in the error - log of the validator. This field can be empty or omitted, in which case no errors will be - expected. -* ***errors.count***: An integer of the total amount of errors that is to be expected. Sometimes - an error is repeated N times, and it's good practice to precisely establish how many errors - should be expected. That said, this field can be empty or omitted. - -## Planning - -The second important task of this utility is to take the wellformed (validated) configuration and -apply it to the VPP dataplane. The overall flow consists of three phases: - -1. Prune phase (remove objects from VPP that are not in the config) -1. Create phase (add objects to VPP that are in the config but not VPP) -1. Sync phase, for each object in the configuration - -When removing things, care has to be taken to remove inner-most objects first. For example, -QinQ/QinAD sub-interfaces should be removed before before their intermediary Dot1Q/Dot1AD. Another -example, MTU of parents should raise before their children, while children should shrink before their -parent. Order matters, so first the tool will ensure all items do not have config which they should -not, then it will ensure that all items that are not yet present, get created in the right order, -and finally all objects are synchronized with the configuration (IP addresses, MTU etc). - - -### Pruning - -1. For any interface that exists in VPP but not in the config: - * Starting with QinQ/QinAD, then Dot1Q/Dot1AD, then (BondEthernets, Tunnels, PHYs) - * Set it admin-state down -1. Retrieve all LCP interfaces from VPP, and retrieve their interface information - * Starting with QinQ/QinAD, then Dot1Q/Dot1AD, then (BondEthernets, Tunnels, PHYs) - * Remove those that do not exist in the config - * Remove those that do exist in the config but have a different encapsulation, for example - if `e0.100` exists with dot1q 100, but has moved to dot1ad 1234. - * Remove those that do exist in the config but have mismatched VPP/LCP interface names, - for example if `e0` was paired with interface Te1/0/0 but has moved to interface Te1/0/1. -1. Retrieve all Loopbacks from VPP - * Remove all IP addresses that are not in the config - * Remove those that do not exist in the config -1. Retrieve all Bridge Domains from VPP - * Remove those that do not exist in the config - * Remove all member interfaces (including BVIs) that are not in the config, return them to - L3 mode - * Remove tag-rewrite options on removed member interfaces if they have encapsulation -1. For L2 Cross Connects from VPP - * For interfaces that do not exist in the config (either as source or target): - * Return the interface to L3 mode - * Remove tag-rewrite options on if it has encapsulation -1. Retrieve all Tunnels from VPP - * Remove all IP addresses that are not in the config - * Remove those that do not exist in the config -1. Retrieve all sub-interfaces from VPP - * Starting with QinQ/QinAD, then Dot1Q/Dot1AD: - * Remove all IP addresses that are not in the config - * Remove those that do not exist in the config - * Remove those that do exist in the config but have a different encapsulation -1. Retrieve all BondEthernets from VPP - * Remove all IP addresses that are not in the config - * Remove those that do not exist in the config - * Remove all member interfaces that are not in the config -1. And finally, for each PHY interface: - * Remove all IP addresses that are not in the config - * If not in the config, return to default (L3 mode, MTU 9000, admin-state down) - -### Creating - -1. Loopbacks -1. BondEthernets -1. Tunnels -1. Sub Interfaces: First Dot1Q and Dot1AD (one tag), and then QinQ and QinAD (two tags) -1. Bridge Domains -1. LCP pairs for Tunnels (TUN type) -1. LCP pairs for PHYs, BondEthernets, Dot1Q/Dot1AD and finally QinQ/QinAD (TAP type) - -### Syncing - -1. For BondEthernets: - * Set MTU of member interfaces - * Add them as slaves, lexicographically sorted by name - * Set their admin-state up - * Ensure LCP has the same MAC as the BondEthernet -1. For Bridge Domains: - * Set the MTU of the member interface (including BVI) - * Add the members (including the BVI) - * Set tag-rewrite options if any of the interfaces have encapsulation -1. For L2 Cross Connects, if applicable: - * Set the MTU of the two interfaces - * Set the L2XC option on both - * Set tag-rewrite options if any of the interfaces have encapsulation -1. Decrease MTU for QinQ/QinAD, then Dot1Q/Dot1AD, then (BondEthernets, Tunnels, PHYs) -1. Raise MTU for (PHYs, Tunnels, BondEthernets), then Dot1Q/Dot1AD, then QinQ/QinAD - * Take special care for PHYs which need a max-frame-size change (some interfaces - must be temporarily set admin-down to change that!) -1. Add IPv4/IPv6 addresses -1. Set admin state for all interfaces - -## Applying - -Finally, once the path planner does its work and orders the operations to reconcile the running dataplane -into the desired configuration, we can apply the configuration. Currently not implemented, pending a bit -of community feedback. - -For now, the path planner works by reading the API configuration state exactly once (at startup), and then -it figures out the CLI calls to print without needing to consult VPP again. This is super useful as it’s a -non-intrusive way to inspect the changes before applying them, and it’s a property I’d like to carry forward. -However, I don’t necessarily think that emitting the CLI statements is the best user experience, it’s more for -the purposes of analysis that they can be useful. What I really want to do is emit API calls after the plan -is created and reviewed/approved, directly reprogramming the VPP dataplane. However, the VPP API set needed -to do this is not 100% baked yet. For example, I observed crashes when tinkering with BVIs and Loopbacks, -and fixed a few obvious errors in the Linux CP API but there are still a few more issues to work through -before I can set the next step with vppcfg. +* [Validation](https://ipng.ch/s/articles/2022/03/27/vppcfg-1.html) +* [Path Planning](https://ipng.ch/s/articles/2022/04/02/vppcfg-2.html) +* [Design - Reconciliation](docs/design.md) +* [User Guide](docs/user-guide.md) +* [Configuration Guide](docs/config-guide.md) diff --git a/docs/config-guide.md b/docs/config-guide.md new file mode 100644 index 0000000..fe30797 --- /dev/null +++ b/docs/config-guide.md @@ -0,0 +1,3 @@ +# A VPP Configuration Utility + +## Configuration Guide diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..c6c637f --- /dev/null +++ b/docs/design.md @@ -0,0 +1,175 @@ +# A VPP Configuration Utility + +## Design + +### YAML Configuration + +The main file that is handled by this program is the **Configuration File**. + +## Validation + +There are three types of validation: _schema_ which ensures that the input YAML has the correct +fields of well known types, _semantic_ which ensures that the configuration doesn't violate +semantic constraints and _runtime_ which ensures that the configuration can be applied to the +VPP daemon. + +### Schema Validators + +First the configuration file is held against a structural validator, provided by [Yamale](https://github.com/23andMe/Yamale/). +Based on a validation schema in `schema.yaml`, the input file is checked for syntax correctness. +For example, a `dot1q` field must be an integer between 1 and 4095, wile an `lcp` string must +match a certain regular expression. After this first pass of syntax validation, I'm certain that +_if_ a field is set, it is of the right type (ie. string, int, enum). + +### Semantic Validators + +A set of semantic validators, each with a unique name, ensure that the _semantics_ of the YAML +are correct. For example, a physical interface cannot have an LCP, addresses or sub-interfaces, +if it is to be a member of a BondEthernet. + +Validators are expected to return a tuple of (bool,[string]) where the boolean signals success +(False meaning the validator rejected the configuration file, True meaning it is known to be +correct), and a list of zero or more strings which contain messages meant for human consumption. + +### Runtime Validators + +After the configuration file is considered syntax and semanticly valid, there is one more set of +checks to perform -- runtime validators ensure that the configuration elements such as physical +network devices (ie. `HundredGigabitEthernet12/0/0` or plugin `lcpng` are present on the system. +It does this by connecting to VPP and querying the runtime state to ensure that what is modeled +in the configuration file is able to be committed. + +## Unit Tests + +It is incredibly important that changes to this codebase, particularly the validators, are well +tested. Unit tests are provided in the `unittests/` directory with a Python test runner in +`tests.py`. Besides regular unittests provided by the Python framework, a YAMLTest is a test which +reads a two-document YAML file, with the first document describing test metadata, and the second +document being a candidate configuration to test, and it then runs all schema and semantic +validators and reports back. + +The format of the YAMLTest is as follows: +``` +test: + description: str() + errors: + expected: list(str()) + count: int() +--- + +``` + +Fields: +* ***description***: A string describing the behavior that is being tested against. Any failure + of the unittest will print this description in the error logs. +* ***errors.expected***: A list of regular expressions, that will be expected to be in the error + log of the validator. This field can be empty or omitted, in which case no errors will be + expected. +* ***errors.count***: An integer of the total amount of errors that is to be expected. Sometimes + an error is repeated N times, and it's good practice to precisely establish how many errors + should be expected. That said, this field can be empty or omitted. + +## Planning + +The second important task of this utility is to take the wellformed (validated) configuration and +apply it to the VPP dataplane. The overall flow consists of three phases: + +1. Prune phase (remove objects from VPP that are not in the config) +1. Create phase (add objects to VPP that are in the config but not VPP) +1. Sync phase, for each object in the configuration + +When removing things, care has to be taken to remove inner-most objects first. For example, +QinQ/QinAD sub-interfaces should be removed before before their intermediary Dot1Q/Dot1AD. Another +example, MTU of parents should raise before their children, while children should shrink before their +parent. Order matters, so first the tool will ensure all items do not have config which they should +not, then it will ensure that all items that are not yet present, get created in the right order, +and finally all objects are synchronized with the configuration (IP addresses, MTU etc). + + +### Pruning + +1. For any interface that exists in VPP but not in the config: + * Starting with QinQ/QinAD, then Dot1Q/Dot1AD, then (BondEthernets, Tunnels, PHYs) + * Set it admin-state down +1. Retrieve all LCP interfaces from VPP, and retrieve their interface information + * Starting with QinQ/QinAD, then Dot1Q/Dot1AD, then (BondEthernets, Tunnels, PHYs) + * Remove those that do not exist in the config + * Remove those that do exist in the config but have a different encapsulation, for example + if `e0.100` exists with dot1q 100, but has moved to dot1ad 1234. + * Remove those that do exist in the config but have mismatched VPP/LCP interface names, + for example if `e0` was paired with interface Te1/0/0 but has moved to interface Te1/0/1. +1. Retrieve all Loopbacks from VPP + * Remove all IP addresses that are not in the config + * Remove those that do not exist in the config +1. Retrieve all Bridge Domains from VPP + * Remove those that do not exist in the config + * Remove all member interfaces (including BVIs) that are not in the config, return them to + L3 mode + * Remove tag-rewrite options on removed member interfaces if they have encapsulation +1. For L2 Cross Connects from VPP + * For interfaces that do not exist in the config (either as source or target): + * Return the interface to L3 mode + * Remove tag-rewrite options on if it has encapsulation +1. Retrieve all Tunnels from VPP + * Remove all IP addresses that are not in the config + * Remove those that do not exist in the config +1. Retrieve all sub-interfaces from VPP + * Starting with QinQ/QinAD, then Dot1Q/Dot1AD: + * Remove all IP addresses that are not in the config + * Remove those that do not exist in the config + * Remove those that do exist in the config but have a different encapsulation +1. Retrieve all BondEthernets from VPP + * Remove all IP addresses that are not in the config + * Remove those that do not exist in the config + * Remove all member interfaces that are not in the config +1. And finally, for each PHY interface: + * Remove all IP addresses that are not in the config + * If not in the config, return to default (L3 mode, MTU 9000, admin-state down) + +### Creating + +1. Loopbacks +1. BondEthernets +1. Tunnels +1. Sub Interfaces: First Dot1Q and Dot1AD (one tag), and then QinQ and QinAD (two tags) +1. Bridge Domains +1. LCP pairs for Tunnels (TUN type) +1. LCP pairs for PHYs, BondEthernets, Dot1Q/Dot1AD and finally QinQ/QinAD (TAP type) + +### Syncing + +1. For BondEthernets: + * Set MTU of member interfaces + * Add them as slaves, lexicographically sorted by name + * Set their admin-state up + * Ensure LCP has the same MAC as the BondEthernet +1. For Bridge Domains: + * Set the MTU of the member interface (including BVI) + * Add the members (including the BVI) + * Set tag-rewrite options if any of the interfaces have encapsulation +1. For L2 Cross Connects, if applicable: + * Set the MTU of the two interfaces + * Set the L2XC option on both + * Set tag-rewrite options if any of the interfaces have encapsulation +1. Decrease MTU for QinQ/QinAD, then Dot1Q/Dot1AD, then (BondEthernets, Tunnels, PHYs) +1. Raise MTU for (PHYs, Tunnels, BondEthernets), then Dot1Q/Dot1AD, then QinQ/QinAD + * Take special care for PHYs which need a max-frame-size change (some interfaces + must be temporarily set admin-down to change that!) +1. Add IPv4/IPv6 addresses +1. Set admin state for all interfaces + +## Applying + +Finally, once the path planner does its work and orders the operations to reconcile the running dataplane +into the desired configuration, we can apply the configuration. Currently not implemented, pending a bit +of community feedback. + +For now, the path planner works by reading the API configuration state exactly once (at startup), and then +it figures out the CLI calls to print without needing to consult VPP again. This is super useful as it’s a +non-intrusive way to inspect the changes before applying them, and it’s a property I’d like to carry forward. +However, I don’t necessarily think that emitting the CLI statements is the best user experience, it’s more for +the purposes of analysis that they can be useful. What I really want to do is emit API calls after the plan +is created and reviewed/approved, directly reprogramming the VPP dataplane. However, the VPP API set needed +to do this is not 100% baked yet. For example, I observed crashes when tinkering with BVIs and Loopbacks, +and fixed a few obvious errors in the Linux CP API but there are still a few more issues to work through +before I can set the next step with vppcfg. diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 0000000..e3ea924 --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,11 @@ +# A VPP Configuration Utility + +## User Guide + +### vppcfg check + +### vppcfg dump + +### vppcfg plan + +### vppcfg apply