Skip to content

Quickstart: Hello World

rmadsen edited this page Sep 8, 2014 · 28 revisions

EOS SDK Quick Start: a Hello World Agent

Introduction

This tutorial will walk you through building, installing, and running your first EOS SDK agent. By the end of this page, you will have created an agent that will say 'Hello' to a name configured via the CLI, and thereby redefining the term "Social Networking." This program, although simple, demonstrates the lifecycle of an agent along with various components of the SDK. We'll first walk you through creating the agent executable, then explain how to run your agent, and, finally, a walkthrough of the EOS SDK code. If you'd like to explore the code without setting running the switch, skip to the usage or code explanation sections.

The complete code for the agent can be found in the examples directory, at [HelloWorld.cpp](<<<<<) for the C++ version of the agent, and [HelloWorld.py](<<<<<) for the Python implementation. Note that you can easily access the raw versions of the example files (for easy wget or scp access) by clicking the 'Raw' link in the upper right of each file's GitHub page.

Getting Started

First, we'll need an EOS instance where we can run our new agent. That means we'll need either a vEOS virtual machine or a physical switch. We then need to install the EOS SDK RPM, which contains the binary implementation of the SDK. Follow the download an installation instructions for information on how to complete both of these steps.

Building your agent

If you want to use the Python version of the agent, there is no need to build anything. All you need to do is copy the script to the switch:

switch# copy https://raw.github.com/aristanetworks/EosSdk/blob/master/examples/HelloWorld.py mnt/flash/HelloWorld.py

For the C++ version of the agent, you'll need a 32-bit Linux environment with the stubs tarball downloaded, unpacked, and built. See [Building your agent](<<<<) for more information on setting up your build environment. Then, copy the HelloWorld.cpp file to your build directory and run:

bash# g++ -std=gnu++0x -o HelloWorldBinary HelloWorld.cpp -leos

which will create an executable named HelloWorldBinary in your current directory. Copy that file to your switch's /mnt/flash/ directory.

Running your Agent

Now that we have a switch with the SDK installed along with an agent executable, let's run the agent!

bash# ssh admin@myAristaSwitch
switch> enable
switch# configure
switch(config)# daemon HelloWorldAgent
switch(config-daemon-HelloWorldAgent)# exec /mnt/flash/HelloWorld.py (or HelloWorldBinary for the C++ binary)
switch(config-daemon-HelloWorldAgent)# no shutdown

You can confirm that the program is running via the show daemon command:

switch(config-daemon-HelloWorldAgent)# show daemon
Agent: HelloWorldAgent (running)
No configuration options stored.

Status:
Data           Value
-------------- ---------------------------
greeting       Welcome! What is your name?

Looks like everything is up and running! Let's now tell our friendly agent our name:

switch(config-daemon-HelloWorldAgent)# option name value Robert Metcalfe
switch(config-daemon-HelloWorldAgent)# show daemon
Agent: HelloWorldAgent (running)
Configuration:
Option       Value
------------ ---------------
name         Robert Metcalfe

Status:
Data           Value
-------------- ----------------------
greeting       Hello Robert Metcalfe!

And, ta-da, our agent said instantly reacted to the name option and said "hi". Feel free to change your name via the option name value <new-name> command and remove your name via no option name, and observe how your newly created social network responds. When we're finished, we can stop our agent using the shutdown command:

switch(config-daemon-HelloWorldAgent)# shutdown
switch(config-daemon-HelloWorldAgent)# show daemon
Agent: HelloWorldAgent (shutdown)
Configuration:
Option       Value
------------ ---------------
name         Robert Metcalfe

Status:
Data           Value
-------------- ------
greeting       Adios!

Anatomy of the HelloWorld agent

In this section, we will explore the code behind the HelloWorld C++ agent. The same explanations hold for the Python variant.

In the beginning...

The agent executable first runs when you enter no shutdown via the CLI. At this point, ProcMgr starts up an instance of your agent, using the command stored in the exec CLI. As we game exec a path to our executable, ProcMgr will just directly run the agent, and thus, will first hit the main() function:

int main(int argc, char ** argv) {
   eos::sdk sdk;
   hello_world_agent agent(sdk);
   sdk.main_loop(argc, argv);
}

The set-up for this agent is simple. We first create an instance of the SDK, using eos::sdk's default constructor. We then construct the hello_world_agent class, which contains the meat of the program's logic. Let's see what happens there:

class hello_world_agent : public eos::agent_handler {
 public:
   eos::agent_mgr * agent_mgr;
   eos::tracer t;

   explicit hello_world_agent(eos::sdk & sdk)
         : eos::agent_handler(sdk.get_agent_mgr()),
           agent_mgr(sdk.get_agent_mgr()),
           t("HelloWorldCppAgent") {
      t.trace0("Agent constructed");
   }
   // ...
};

The first thing to notice is that the hello_world_agent inherits from eos::agent_handler. All agents should have at least one class that subclasses the agent_handler, as this handler provides agent-specific callbacks alerting you of startup, shutdown and configuration events. In many cases, your main class will inherit from several handlers, each providing specific on_xxx() callbacks that let you react to other network events. As our HelloWorld agent only cares about agent options, we only need to subclass from the agent_handler.

In the hello_world_agent constructor, we go ahead and initialize various elements:

  • our superclass, which takes an eos::agent_mgr...
  • ... which we also store in a class agent_mgr * instance variable
  • and finally an eos::tracer object, which lets our agent output debug trace statements to its log file when tracing is enabled

At this point, all of the relevant classes, data structures, and logic are created, so, back in our main function, we start the main_loop. This function never returns and instead creates the continuously running event loop, managed by the SDK.

Before this point, our program has simply been a C++ class that has no connection to Sysdb. This means that none of the on_xxx() callbacks will fire, and the agent_mgr will not be able to set or read any state. Our call to start the main_loop changes this: the SDK connects to Sysdb, synchronizes any state needed by the agent_mgr, and registers itself for any relevant notifications. When this dance is complete, the agent_handler's on_initialized method is called, a method that our hello_world_agent overrides:

   void on_initialized() {
      t.trace0("Initialized");
      std::string name = agent_mgr->agent_option("name");
      if(name.empty()) {
         // No name initially set.
         agent_mgr->status_set("greeting", "Welcome! What is your name?");
      } else {
         // Handle initial state.
         on_agent_option("name", name);
      }
   }

The first thing we do upon initialization is check if Sysdb already has any relevant state set. For our agent, we just care if somebody has told us their name, so we go and ask the agent_mgr for the value corresponding to the agent_option called "name". If the option is not set, the call to agent_option will result in an empty string (as documented in agent.h), so we'll just set a welcome message. Otherwise, there was already a name set, so we jump to the on_agent_option logic, which handles handles the agent name.

You may wonder when any initial state could have been created. There are many ways that this could have happened:

  • The user may have set the name option before running no shutdown from the CLI, thus seeding Sysdb with a name already.
  • The switch may have been rebooted, and the start-up configuration contained the command option name value <name> for this daemon, meaning the configuration was set before your agent was ever enabled.
  • A stateful switchover event happened, meaning that control transferred from one supervisor to another on a modular system. To your agent, this just looks like an agent restart where state already was set.
  • Your agent was buggy and crashed, causing ProcMgr to restart it.
  • Someone, or some other program, set that state in Sysdb.
  • Or one of many other causes.

In any case, your agent was started, and needs to make sure it can sanely handle what is already in Sysdb. At this point, there is nothing more to do so we return control to the event loop and wait for another event to occur.

Let's assume the co-inventor of Ethernet now enters his name at the EOS CLI: option name value Robert Metcalfe. Under the hood, this command sets a configuration option for agent in Sysdb. Sysdb notices that we registered for notifications on this state, and thus notifies this process of the new state via a connection that the SDK established when starting the main_loop. The SDK, in turn, transforms this update into a std::string key/value pair and calls on_agent_option with the new value. Since we overrode this method as well, our callback runs:

   void on_agent_option(std::string const & option_name,
                        std::string const & value) {
      if(option_name == "name") {
         if(value.empty()) {
            // User deleted the 'name' option
            t.trace3("Name deleted");
            agent_mgr->status_set("greeting", "Goodbye!");
         } else {
            // Now *this* is what social networking is all
            // about. Somebody set, or changed, the name option. Let's
            // do some salutations!
            t.trace3("Saying hi to %s", value.c_str());
            agent_mgr->status_set("greeting", "Hello " + value + "!");
         }
      }
   }

This method is straightforward: if the 'name' option changed, we go ahead and update the greeting appropriately. We then return to the event loop to await further notifications.

The last piece of this function is the cleanup mechanism. If this agent is ever shutdown, we are given the chance to cleanup any status. Note that this cleanup is not guaranteed to run: the underlying agent process could be abruptly terminated by the system for any number of reasons. However, if we are cleanly disabled, the agent_handler's on_enabled callback will fire:

   void on_agent_enabled(bool enabled) {
      if (!enabled) {
         t.trace0("Shutting down");
         agent_mgr->status_set("greeting", "Adios!");
         agent_mgr->agent_shutdown_complete_is(true);
      }
   }

In this case we don't have to do much: we just publish a goodbye message and alert the agent_mgr that we have completed cleanup. Under the hood, the SDK then exits the event loop and the process exits.

Congratulations on finishing your first walkthrough of an EOS SDK agent!