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