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