VPP Object Model (VOM)

The VOM is a C++ library for use by clients/agents of VPP for programming
state. It uses the binary APIs to do so. Various other common client side
functions are also provided. Please see om.hpp for a more detailed description.

Change-Id: Ib756bfe99817093815a9e26ccf464aa5583fc523
Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
Co-authored-by: Mohsin Kazmi <sykazmi@cisco.com>
diff --git a/src/vpp-api/vom/om.hpp b/src/vpp-api/vom/om.hpp
new file mode 100644
index 0000000..f470c5e
--- /dev/null
+++ b/src/vpp-api/vom/om.hpp
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __VOM_OM_H__
+#define __VOM_OM_H__
+
+#include <memory>
+#include <set>
+
+#include "vom/client_db.hpp"
+#include "vom/hw.hpp"
+
+/**
+
+The VPP Object Model (VOM) library.
+
+Before we begin, a glossary of terms:
+   - Agent or client: A user mode process that links to and uses the VOM library
+     to programme VPP
+   - VPP: A running instance of VPP
+   - High Availability (HA): Scenarios where the client and/or VPP restart with
+     minimal service interruption.
+   - CReate, Update, Delete (CRUD): An API style where the producer issues
+     notifications to changes to objects
+
+The VOM is a C++ library that models entities in VPP as C++ classes. The
+ relationships between VOM objects and VPP entities is not always 1:1. Some
+ effort has been made to construct a higher level, more abstract API to VPP
+ programming*.
+The client programming model is simple (or at least I intended it to be..). The
+client deals in ‘desired’ state, that is, it expresses the objects it wants to
+exists (in VPP) and the properties that the object should have, i.e**;
+    Interface af1(“my-af-packet-1”, AFPACKET, admin::UP);
+Then the client ‘writes’ this object into the ‘model’
+    OM::write(“clients-thing-1”, af1);
+
+“clients-thing-1” is a description of the entity within the client’s domain that
+‘owns’ (or has locked or has a reference to) the VOM object. There can be many
+owners of each VOM object. It will be the last owner’s update that will be
+programmed in VPP. This model means that the client is not burdened with
+maintaining which of its objects have created which VOM objects. If the client
+is itself driven by a CRUD API, then create notifications are implemented as
+ above. Update notifications add two extra statements;
+    OM::mark(“clients-thing-1”);
+    … do writes ….
+    OM::sweep(“clients-thing-1”);
+These ‘mark’ and ‘sweep’ statements are indications to OM that firstly, indicate
+that all the objects owned by “clients-thing-1” are now stale, i.e that the
+client may no longer need them. If one of the subsequent writes should update a
+stale object, then it is no longer stale. The sweep statement will ‘remove’ all
+the remaining stale objects. In this model, the client does not need to maintain
+the mapping of VOM objects to its own objects – it can simply express what it
+needs now.
+The delete notification is simply:
+     OM::remove(“clients-thing-1”);
+Which will remove all the objects in VOM that are owned by “clients-thing-1”.
+Where ‘remove’ in this sense means unlock and unreference, the VOM object, and
+VPP state, will only be truly removed once there are no more owners. This is
+equivalent to a mark & sweep with no intermediate writes.
+
+To provide this client side model the VOM is a stateful library, meaning that
+for each entity it creates in VPP, VOM maintains its own representation of that
+object. VOM can therefore be memory hungry. The desired state is expressed by
+the client, the ‘actual’ state is maintained by VOM. VOM will consolidate the
+two states when the client writes to the OM and thus issue VPP only the changes
+required.
+
+The concepts of ownership and statefulness also allow the support for HA
+scenarios.
+VPP restart: When VPP restarts, VOM will reconnect and ‘replay’ its state, in
+dependency order, to VPP. The client does not need to regenerate its desired
+state.
+Client restart: when the client restarts, VOM will read/dump the current state
+of all VPP objects and store them in the OM owned by the special owner “boot”.
+As the client reprogrammes its desired state, objects will become owned by both
+the boot process and the client. At the point in time, as determined by the
+client, all stale state, that owned only by boot, can be purged. Hence the
+system reaches the correct final state, with no interruption to VPP forwarding.
+
+
+Basic Design:
+
+Each object in VOM (i.e. an interface, route, bridge-domain, etc) is stored in a
+per-type object database, with an object-type specific key. This ‘singular’ DB
+has a value-type of a weak pointer to the object. I use the term ‘singular’ to
+refer to the instance of the object stored in these databases, to be distinct
+from the instances the client constructs to represent desired state.
+The ‘client’ DB maintains the mapping of owner to object. The value type of the
+client DB is a shared pointer to the singular instance of the owned object.
+Once all the owners are gone, and all the shared pointers are destroyed, the
+singular instance is also destroyed.
+
+Each VOM object has some basic behaviour:
+  update: issue to VPP an update to this object’s state. This could include the
+          create
+  sweep: delete the VPP entity – called when the object is destroyed.
+  replay: issue to VPP all the commands needed to re-programme (VPP restart HA
+          scenario)
+  populate: read state from VPP and add it to the OM (client restart HA
+scenario)
+
+The object code is boiler-plate, in some cases (like the ACLs) even template.
+The objects are purposefully left as simple, functionality free as possible.
+
+Communication with VPP is through a ‘queue’ of ‘commands’. A command is
+essentially an object wrapper around a VPP binary API call (although we do use
+the VAPI C++ bindings too). Commands come in three flavours:
+  RPC: do this; done.
+  DUMP: give me all of these things; here you go
+  EVENT; tell me about these events; here’s one …. Here’s one…. Oh here’s
+         another….. etc.
+
+RPC and DUMP commands are handled synchronously. Therefore on return from
+OM::write(…) VPP has been issued with the request and responded. EVENTs are
+asynchronous and will be delivered to the listeners in a different thread – so
+beware!!
+
+* As such VOM provides some level of insulation to the changes to the VPP
+  binary API.
+** some of the type names are shorten for brevity’s sake.
+
+*/
+namespace VOM {
+/**
+ * The interface to writing objects into VPP OM.
+ */
+class OM
+{
+public:
+  /**
+   * A class providing the RAII pattern for mark and sweep
+   */
+  class mark_n_sweep
+  {
+  public:
+    /**
+     * Constructor - will call mark on the key
+     */
+    mark_n_sweep(const client_db::key_t& key);
+
+    /**
+     * Destructor - will call sweep on the key
+     */
+    ~mark_n_sweep();
+
+  private:
+    /**
+     * no copies
+     */
+    mark_n_sweep(const mark_n_sweep& ms) = delete;
+
+    /**
+     * The client whose state we are guarding.
+     */
+    client_db::key_t m_key;
+  };
+
+  /**
+   * Init
+   */
+  static void init();
+
+  /**
+   * populate the OM with state read from HW.
+   */
+  static void populate(const client_db::key_t& key);
+
+  /**
+   * Mark all state owned by this key as stale
+   */
+  static void mark(const client_db::key_t& key);
+
+  /**
+   * Sweep all the key's objects that are stale
+   */
+  static void sweep(const client_db::key_t& key);
+
+  /**
+   * Replay all of the objects to HW.
+   */
+  static void replay(void);
+
+  /**
+   * Make the State in VPP reflect the expressed desired state.
+   *  But don't call the HW - use this whilst processing dumped
+   *  data from HW
+   */
+  template <typename OBJ>
+  static rc_t commit(const client_db::key_t& key, const OBJ& obj)
+  {
+    rc_t rc = rc_t::OK;
+
+    HW::disable();
+    rc = OM::write(key, obj);
+    HW::enable();
+
+    return (rc);
+  }
+
+  /**
+   * Make the State in VPP reflect the expressed desired state.
+   *  After processing all the objects in the queue, in FIFO order,
+   *  any remaining state owned by the client_db::key_t is purged.
+   * This is a template function so the object's update() function is
+   * always called with the derived type.
+   */
+  template <typename OBJ>
+  static rc_t write(const client_db::key_t& key, const OBJ& obj)
+  {
+    rc_t rc = rc_t::OK;
+
+    /*
+     * Find the singular instance another owner may have created.
+     * this always returns something.
+     */
+    std::shared_ptr<OBJ> inst = obj.singular();
+
+    /*
+     * Update the existing object with the new desired state
+     */
+    inst->update(obj);
+
+    /*
+     * Find if the object already stored on behalf of this key.
+     * and mark them stale
+     */
+    object_ref_list& objs = m_db->find(key);
+
+    /*
+     * Iterate through this list to find a matchin' object
+     * to the one requested.
+     */
+    auto match_ptr = [inst](const object_ref& oref) {
+      return (inst == oref.obj());
+    };
+    auto it = std::find_if(objs.begin(), objs.end(), match_ptr);
+
+    if (it != objs.end()) {
+      /*
+       * yes, this key already owns this object.
+       */
+      it->clear();
+    } else {
+      /*
+       * Add the singular instance to the owners list
+       */
+      objs.insert(object_ref(inst));
+    }
+
+    return (HW::write());
+  }
+
+  /**
+   * Remove all object in the OM referenced by the key
+   */
+  static void remove(const client_db::key_t& key);
+
+  /**
+   * Print each of the object in the DB into the stream provided
+   */
+  static void dump(const client_db::key_t& key, std::ostream& os);
+
+  /**
+   * Print each of the KEYS
+   */
+  static void dump(std::ostream& os);
+
+  /**
+   * Class definition for listeners to OM events
+   */
+  class listener
+  {
+  public:
+    listener() = default;
+    virtual ~listener() = default;
+
+    /**
+     * Handle a populate event
+     */
+    virtual void handle_populate(const client_db::key_t& key) = 0;
+
+    /**
+     * Handle a replay event
+     */
+    virtual void handle_replay() = 0;
+
+    /**
+     * Get the sortable Id of the listener
+     */
+    virtual dependency_t order() const = 0;
+
+    /**
+     * less than operator for set sorting
+     */
+    bool operator<(const listener& listener) const
+    {
+      return (order() < listener.order());
+    }
+  };
+
+  /**
+   * Register a listener of events
+   */
+  static bool register_listener(listener* listener);
+
+private:
+  /**
+   * Database of object state created for each key
+   */
+  static client_db* m_db;
+
+  /**
+   * Comparator to keep the pointers to listeners in sorted order
+   */
+  struct listener_comparator_t
+  {
+    bool operator()(const listener* l1, const listener* l2) const
+    {
+      return (l1->order() < l2->order());
+    }
+  };
+
+  /**
+   * convenient typedef for the sorted set of listeners
+   */
+  typedef std::multiset<listener*, listener_comparator_t> listener_list;
+
+  /**
+   * The listeners for events
+   */
+  static std::unique_ptr<listener_list> m_listeners;
+};
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "mozilla")
+ * End:
+ */
+
+#endif