| /* |
| * 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 <algorithm> |
| #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 |