State validation
In the last three sections of this chapter, we pushed device configs without verifying that the configuration changes had the desired effect. This is because we need all devices configured before we can validate the resulting converged operational state. Now, with all the code examples from the OpenAPI, JSON-RPC, and RESTCONF sections executed against the lab topology, we can verify whether we achieved our configuration intent—establish end-to-end reachability between loopback IP addresses of all three devices.
In this section, we’ll use the same protocols and modeling language we used earlier in this chapter to validate that each lab device can see the loopback IP address of the other two lab devices in its Forwarding Information Base (FIB) table. You can find the complete code for this section in the ch08/state
directory (refer to the Further reading section) of this book’s GitHub repository. Next, we’ll examine a single example of how you can do this with Arista’s cEOS (ceos
) lab device.
Operational state modeling
One thing we need to be mindful of when talking about the operational state of a network element is the difference between the applied and the derived state, as described by the YANG operational state IETF draft (refer to the Further reading section). The former refers to the currently active device configuration and should reflect what an operator has already applied. The latter is a set of read-only values that result from the device’s internal operations, such as CPU or memory utilization, and interaction with external elements, such as packet counters or BGP neighbor state. Although we aren’t explicitly mentioning it when we’re talking about an operational state, assume we’re referring to the derived state unless we state otherwise.
Historically, there’ve been different ways to model the device’s operational state in YANG:
- You could either enclose everything in a top-level container or read from a separate
state
datastore, completely distinct from theconfig
container/datastore we use for configuration management. - Another way is to create a separate
state
container for every YANG sub-tree alongside theconfig
container. This is what the YANG operational state IETF draft (refer to the Further reading section) describes.
Depending on which approach you use, you may need to adjust how you construct your RPC request. For example, the srl
device needs an explicit reference to the state
datastore. What we show in the next code example is the alternative approach, where you retrieve a part of the YANG sub-tree and extract the relevant state information from it.
It’s worth noting that OpenAPI is less strict about the structure and composition of its models and the state may come from a different part of a tree or require a specific query parameter to reference the operational datastore, depending on the implementation.
Operational state processing
Configuration management workflows typically involve the processing of some input data to generate a device-specific configuration. This is a common workflow that we often use to show the capabilities of an API. But there is an equally important workflow that involves operators retrieving state data from a network device, which they process and verify. In that case, the information flows in the opposite direction—from a network device to a client application.
At the beginning of this chapter, we discussed the configuration management workflow, so now we want to give a high-level overview of the state retrieval workflow:
- We start by querying a remote API endpoint, represented by a set of URL and HTTP query parameters.
- We receive an HTTP response, which has a binary payload attached to it.
- We unmarshal this payload into a Go struct that follows the device’s data model.
- Inside this struct, we look at the relevant parts of the state we can extract and evaluate.
The following code snippet from the ch08/state
program (refer to the Further reading section) is a concrete example of this workflow. The program structure follows the same pattern we described in the State validation section of Chapter 6, Configuration Management. Hence, in this chapter, we’ll only zoom in on the most relevant part—the GetRoutes
function, which connects to the ceos
device and retrieves the content of its routing table.
It starts by building an HTTP request with the device-specific login information:
func (r CEOS) GetRoutes(wg *sync.WaitGroup) { client := resty.NewWithClient(&http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true}, }, }) client.SetBaseURL("https://" + r.Hostname + ":6020") client.SetBasicAuth(r.Username, r.Password) resp, err := client.R(). SetHeader("Accept", "application/yang-data+json"). Get(fmt.Sprintf("/restconf/data/network-instances/network-instance=%s/afts", "default")) /* ... <continues next > ... */ }
The Abstract Forwarding Table (AFT) in the code example is an OpenConfig representation of the FIB (routing) table and the GET API call retrieves a JSON representation of the default Virtual Routing and Forwarding (VRF) routing table.
Next, we create an instance of the Go struct corresponding to the part of the YANG tree we queried and pass it to the Unmarshal
function for deserialization. The resulting Go struct now has one Ipv4Entry
value for each entry in the default FIB and we store that list of prefixes in the out
slice:
import eosAPI "restconf/pkg/eos" func (r CEOS) GetRoutes(wg *sync.WaitGroup) { /* ... <continues from before > ... */ response := &eosAPI.NetworkInstance_Afts{} err := eosAPI.Unmarshal(resp.Body(), response) // process error out := []string{} for key := range response.Ipv4Entry { out = append(out, key) } /* ... <omitted for brevity > ... */ go checkRoutes(r.Hostname, out, expectedRoutes, wg) }
In this example, we import the eos
package (restconf/pkg/eos
) we auto-generated in the RESTCONF section of this chapter, which lives outside the root directory of this program. To do this, we add the replace restconf => ../restconf/
instruction to this program’s go.mod
file (ch08/state/go.mod
; refer to the Further reading section).
For the remaining lab devices, we follow a similar state retrieval workflow. The only difference is in the YANG paths and the model-based Go structs we use for deserialization. You can find the full program code in the ch08/state
directory (refer to the Further reading section) of this book’s GitHub repository.
In this chapter, we have covered network APIs based on HTTP version 1.1 that use common encoding formats, such as JSON. Although HTTP is still very popular and this is unlikely to change soon, it has its own limitations that may manifest themselves in large-scale deployments. HTTP 1.1 is a text-based protocol, which means it’s not efficient on the wire and its client-server origins make it difficult to adapt it for bi-directional streaming. The next version of this protocol, HTTP/2, overcomes these shortcomings. HTTP/2 is the transport protocol of the gRPC framework, which is what we’ll examine in the next section.