From 812ed39f9da336310e815c361ab5a9f118657d94 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Mon, 16 Oct 2017 04:20:13 -0700 Subject: 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 Co-authored-by: Mohsin Kazmi --- src/vpp-api/vom/om.hpp | 355 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 src/vpp-api/vom/om.hpp (limited to 'src/vpp-api/vom/om.hpp') diff --git a/src/vpp-api/vom/om.hpp b/src/vpp-api/vom/om.hpp new file mode 100644 index 00000000000..f470c5ee185 --- /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 +#include + +#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 + 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 + 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 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_list; + + /** + * The listeners for events + */ + static std::unique_ptr m_listeners; +}; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "mozilla") + * End: + */ + +#endif -- cgit 1.2.3-korg