aboutsummaryrefslogtreecommitdiffstats
path: root/src/vpp-api/vom/om.hpp
blob: e68d5ea0017633a18294c99cd43a0a2bcab16456 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
 * 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