Skip to content

Using the SDK

Ryan Madsen edited this page May 21, 2015 · 19 revisions

Understanding the SDK

EOS SDK comprises a set of API calls that let you directly interact with EOS. These APIs are split into different C++ header files, where each file abstracts a single switching concept. For example, acl.h lets you manage and configure Access Control Lists, while eth_intf.h exposes data for logical ethernet interfaces (including both LAGs and front-panel ports).

This document will describe the various components and idioms that the SDK uses across these modules to maintain consistency. The functional purpose of these modules can be explored in the capabilities of the SDK documentation.

SDK Components

There are three main types of classes that you will encounter in the SDK. They are mgrs, which let you get and set state, handlers, which let you react to state changing, and value_t types, which provide standardized representations of internal data types.

Managers

Manager objects allow you to observe and manipulate state in Sysdb. Each header provides one or more _mgr classes, usually defined at the bottom of the file, and each _mgr is responsible for interacting with one discrete SDK concept. As an example, let's examine intf.h's intf_mgr, a class that lets you interact with the base attributes shared by all interface objects on EOS.

To get hold of a _mgr object, you do not construct it directly. Instead, use the get_xxx_mgr() methods provided by the base sdk object before commencing the main_loop:

eos::sdk sdk;
eos::intf_mgr * interface_mgr = sdk.get_intf_mgr()

The sdk.get_intf_mgr() call returns a pointer to the corresponding intf_mgr class and registers this manager as "requested". If called again, it will simply return the same pointer.

Note that this mgr class is not usable directly after it is grabbed. The interface_mgr will remain in an un-initialized state until the on_initialized callback is run. If you attempt to use the manager before it is initialized, a eos::error will be thrown, alerting you that the mgr is not yet usable. If you wish to register use of a manager without storing a reference to it, you may use the init_xxx_mgr function before entering hte mainloop, e.g. sdk.init_intf_mgr().

The reason for this two-step initialization process is because the SDK only keeps portions of the state hierarchy that the agent will need. When you first call get_intf_mgr, we note that you are interested in interface state. Then, when you start the main_loop, the SDK is able to synchronize all of the necessary state hierarchy from Sysdb and register for future state updates. This process is described in further detail in the EOS architecture and agent lifecycle documents.

Once the manager is initialized, you are able to call its various methods. As an example, assume your agent needs to check if an interface is configured to be enabled. The intf_mgr provides the following method:

/// Returns true if the given interface is configured to be enabled
virtual bool admin_enabled(intf_id_t) const = 0;                   

As the comment describes, this const method retrieves the "enabled" state of the interface, and can be used as follows:

eos::intf_id_t et1("Ethernet1");
bool enabled = interface_mgr->admin_enabled(et1);
if (enabled) {
   printf("Ethernet1 is enabled!");
} else {
   printf("Ethernet1 is disabled :(");
}

Note that this method, like all SDK "getter" methods, pulls data from the its local copy of the state hierarchy, and never block.

Managers often provide "setter" methods to change state in Sysdb, too. For example, if you wish to administratively disable an interface, you use intf_mgr's admin_enabled_is method:

/// Configures the enabled status of the interface 
virtual void admin_enabled_is(intf_id_t, bool) = 0;

And call it as follows:

interface_mgr->admin_enabled_is(et1, false);

This invocation updates your agent's local state and immediately returns to your program. When your agent eventually yields control to the event loop, the updated state is serialized to Sysdb, where it will then be pushed to all other agents.

A note on naming conventions: "getter" methods are named directly after the data they are accessing—so xxx() implies a method that will return the xxx data. Setters are usually suffixed with _is, though if the setter is applied to a member of a collection, it is suffixed with _set. Thus, xxx_is("new value") will set xxx to "new value", while xxx_set("some key", "new value") will insert "new value" in the xxx collection, indexed by "some key".

Managers commonly expose a number of other functions, like exists() to check if a member exists in a collection, or xxx_iter() to get an iterator object that can trace over each member of a collection. Other standard functions are described below.

Handlers

EOS SDK handler classes let agents react to state updates and other events. A handler is a simple class that exposes a number of methods prefixed with on_ that correlate to different events. Agents that are interested in a particular event should inherit from the handler class and override the on_xxx() methods in order to receive notifications on that event. As an example, let's consider a simple handler which wants to receive notifications when an interface goes down.

#include <eos/intf.h>
#include <eos/sdk.h>

class intf_reactor : public eos::intf_handler {
 public:
   intf_reactor(eos::sdk & sdk) : intf_handler(sdk.get_intf_mgr()) {
      // ... do any initialization needed and register this handler
      // for all interface notifications.
      watch_all_intfs(true);
   }
   
   void on_oper_status(eos::intf_id_t intf, eos::oper_status_t status) {
      if (status != eos::INTF_OPER_UP) {
         // `intf` is no longer operational!    
      }
   }
};

As this handler illustrates, we first inherit from the intf_handler that is provided by intf.h, and make sure to call its constructor in our initialization list, passing a reference to the parent intf_mgr. This handler provides a number of notifiers, including notifications triggered when an interface is created, deleted, administratively disabled, or changes operational status. In this example, we only care about the status of the interface, so we override on_oper_status. This function specifies two parameters: the interface,intf, that changed state, and the new status (defined in an enum in types/intf.h.

When the forwarding agent updates intf's state in Sysdb, this value is propogated to the agent, which results in this callback being triggered. Once the agent is finished reacting to the new state, control is returned to the event loop, and we resume waiting for events to occur.

Most handlers, like the intf_handler in the above example, require you to explicitly register for notifications using the watch_xxx() methods defined within the handler. Other handlers, like the agent_handler, automatically register for known events. This split occurs for handlers that can watch for events at various granularities—either watch individual entries, or watch an entire collection of entries. Comments for each handler elucidate their proper usage.

Value types

Value types are constructs which store a defined set of state; they provide a stable abstraction between the internal representation of the type and the published API that libeos exposes. In the SDK, values end with the _t suffix and are usually defined in the eos/types directory.

A value type can be very simple and just represent a single piece of data. For example, an intf_id_t represents a single interface:

eos::intf_id_t an_interface("Management1");
assert(an_interface.intf_type() == eos::INTF_TYPE_MANAGEMENT);

However, value types can can also be complex—containing collections and other value types nested in its attributes. Consider the semantics for creating new route.

eos::ip_prefix_t prefix("10.2.3.4/32");
eos::ip_route_key_t rkey(prefix);
rkey.preference_is(42);
eos::ip_route_t my_route(rkey);
assert(my_route.key().preference() == 42);
assert(rkey.prefix().af() == eos::AF_IPV4);

As the above example illustrates, to alter an attribute xxx on a value_t, call the xxx_is(new_value) method. To get the value of that attribute, simply call xxx. Like managers, operations on collections also expose xxx_set() and xxx_del() methods to manipulate members of that collection.

However, modifying a value type does not result in any changed configuration; no state updates are relayed to Sysdb after we call key.preference_is(42). This object is simply a container for the data. Only when you call a function on a manager, like ip_route_mgr->ip_route_is(my_route), will this state actually be transformed into the internal representation and pushed to Sysdb.

Similarly, values maintain copy-by-value semantics: value a == b if a's attributes match b's attributes. For ease-of-use, value types define operator== and operator!= functions, and many also expose to_string, operator<, and hash functions. Note that these may not be "cheap" for large value types, as comparisons and string/hash computations may require many operations.

Common SDK Idioms and paradigms

Error handling

If you try and perform an illegal operation, EOS SDK will panic and throw an instance of eos::error. We try and keep the number of exceptions low, and only panic when an agent attempts to install some invalid configuration, or when an agent calls a method that isn't supported on a given release or platform. Specific error classes are documented in eos/exception.h

If you'd prefer that EOS SDK does not throw exceptions, you can install your own panic_handler. See the documentation for exception_handler_is in eos/panic.h.

Getters

Getters return a default value instead of panicing, when possible. For example, if you'd like to check whether the switch has a learned an ARP entry, you can use the neighbor_table_mgr->neighbor_entry_status(...) accessor to look up the entry based on an IP address. If there is a corresponding ARP entry learned, you'll receive a filled-in instance of a eos::neighbor_entry_t. If there is no matching entry, instead the function will return an empty eos::neighbor_entry_t().

Getters will panic if you call it with invalid parameters. For instance, eth_lag_intf_mgr::eth_lag_intf_member_iter will panic if you try and get the LAG members for a non Port-Channel interface. In these cases, your agent is doing something wrong: it just makes no sense to call this method for a Vlan interface.

Setters and Deleters

Setters (_is and _set methods) and deleters (_del methods) off of manager classes are idempotent: if you call that function twice in a row, the second operation will successfully run and cause no state changes. This behavior is very useful, the alternative of checking existing state before writing new state is a painfully annoying paradigm.

Iterators

Managers that work on tables or collections of items expose an iterator to let agents traverse the collection. The object an iterator yields is documented in comments, but can also be discovered by examining the first parameter passed to the parent iter_base class. For example, the eos::intf_iter_t class inherits from iter_base<intf_id_t, intf_iter_impl>, which means that the iterator will yield an eos::intf_id_t for pass. In C++, iterators can be dereferenced to get the underlying type, as seen in the following example:

eos::intf_iter_t intf_iter = intf_mgr->intf_iter();
for (; intf_iter; intf_iter++) {
   // Print the name + description of all interfaces exposed 
   // by the intf manager.
   std::string name = intf_iter->to_string();
   std::string desc = intf_mgr->description(*intf_iter);
   printf("Interface %s has description %s", name.c_str(), desc.c_str());
}

And iterators in Python are similarly easy to use:

for intf in intf_mgr.intf_iter():
   # Print the name + description of all interfaces exposed 
   # by the intf manager.
   name = intf.to_string()
   desc = intf_mgr.description(intf)
   print "Interface", name, "has description", desc

Exists

Managers that expose collections will often have an exists(key) method that returns true if key can be handled by that collection.