Replace supported admonitions with rst directives
[docs.git] / docs / developer-guide / opflex-libopflex-developer-guide.rst
1 OpFlex libopflex Developer Guide
2 ================================
3
4 Overview
5 --------
6
7 The OpFlex framework allows you to develop agents that can communicate
8 using the OpFlex protocol and act as a policy element in an OpFlex-based
9 distributed control system. The OpFlex architecture provides a
10 distributed control system based on a declarative policy information
11 model. The policies are defined at a logically centralized policy
12 repository and enforced within a set of distributed policy elements. The
13 policy repository communicates with the subordinate policy elements
14 using the OpFlex control protocol. This protocol allows for
15 bidirectional communication of policy, events, statistics, and faults.
16
17 Rather than simply providing access to the OpFlex protocol, this
18 framework allows you to directly manipulate a management information
19 tree containing a hierarchy of managed objects. This tree is kept in
20 sync as needed with other policy elements in the system, and you are
21 automatically notified when important changes to the model occur.
22 Additionally, we can ensure that only those managed objects that are
23 important to the local policy element are synchronized locally.
24
25 Object Model
26 ~~~~~~~~~~~~
27
28 Interactions with the OpFlex framework happen through the management
29 information tree. This is a tree of managed objects defined by an object
30 model specific to your application. There are a few important major
31 category of objects that can appear in the model.
32
33 -  First, there is the policy object. A policy object represents some
34    data related to a policy that describes a user intent for how the
35    system should behave. A policy object is stored in the policy
36    repository which is the source of "truth" for this object.
37
38 -  Second, there is an endpoint object. A endpoint represents an entity
39    in the system to which we want to apply policy, which could be a
40    network interface, a storage array, or other relevent policy
41    endpoint. Endpoints are discovered and reported by policy elements
42    locally, and are synchronized into the endpoint repository. The
43    originating policy element is the source of truth for the endpoints
44    it discovers. Policy elements can retrieve information about
45    endpoints discovered by other policy elements by resolving endpoints
46    from the endpoint repository.
47
48 -  Third, there is the observable object. An observable object
49    represents some state related to the operational status or health of
50    the policy element. Observable objects will be reported to the
51    observer.
52
53 -  Finally, there is the local-only object. This is the simplest object
54    because it exists only local to a particular policy element. These
55    objects can be used to store state specific to that policy element,
56    or as helpers to resolve other objects. Read on to learn more.
57
58 You can use the genie tool that is included with the framework to
59 produce your application model along with a set of generated accessor
60 classes that can work with this framework library. You should refer to
61 the documentation that accompanies the genie tool for information on how
62 to use to to generate your object model. Later in this guide, we’ll go
63 through examples of how to use the generated managed object accessor
64 classes.
65
66 Programming by Side Effect
67 ~~~~~~~~~~~~~~~~~~~~~~~~~~
68
69 When developing software on the OpFlex framework, you’ll need to think
70 in a slightly different way. Rather than calling an API function that
71 would perform some specific action, you’ll need to write a managed
72 object to the managed object database. Writing that object to the store
73 will trigger the side effect of performing the action that you want.
74
75 For example, a policy element will need to have a component responsible
76 for discovering policy endpoints. When it discovers a policy endpoint,
77 it would write an endpoint object into the managed object database. That
78 endpoint object will contain a reference to policy that is relevant to
79 the endpoint object. This will trigger a whole cascade of events. First,
80 the framework will notice that an endpoint object has been created and
81 it will write it to the endpoint repository. Second, the framework to
82 will attempt to resolve the unresolved reference to the relevent policy
83 object. There might be a whole chain of policy resolutions that will be
84 automatically performed to download all the relevent policy until there
85 are no longer any dangling references.
86
87 As long as there is a locally-created object in the system with a
88 reference to that policy, the framework will continually ensure that the
89 policy and any transitive policies are kept up to date. The policy
90 element can subscribe to updates to these policy classes that will be
91 invoked either the first time the policy is resolved or any time the
92 policy changes.
93
94 A consequence of this design is that the managed object database can be
95 temporarily in an inconsistent state with unresolved dangling
96 references. Eventually, however, the inconsistency will be fully
97 resolved. The policy element must be able to cleanly handle
98 partially-resolved or inconsistent state and eventually reach the
99 correct state as it receives these update notifications. Note that, in
100 the OpFlex architecture, when there is no policy that specifically
101 allows a particular action, that action must be prevented.
102
103 Let’s cover one slightly more complex example. If a policy element needs
104 to discover information about an endpoint that is not local to that
105 policy element, it will need to retrieve that information from the
106 endpoint repository. However, just as there is no API call to retrieve a
107 policy object from the policy repository, there is no API call to
108 retrieve an endpoint from the endpoint repository.
109
110 To get this information, the policy element needs to create a local-only
111 object that references the endpoint. Once it creates this local-only
112 object, if the endpoint is not already resolved, the framework will
113 notice the dangling reference and automatically resolve the endpoint
114 from the endpoint respository. When the endpoint resolution completes,
115 the framework deliver an update notification to the policy element. The
116 policy element will continue to receive any updates related to that
117 endpoint until the policy element remove the local-only reference to the
118 object. Once this occurs, the framework can garbage-collect any
119 unreferenced objects.
120
121 Threading and Ownership
122 ~~~~~~~~~~~~~~~~~~~~~~~
123
124 The OpFlex framework uses a somewhat unique threading model. Each
125 managed object in the system belongs to a particular owner. An owner
126 would typically be a single thread that is reponsible for all updates to
127 objects with that owner. Though anything can read the state of a managed
128 object, only the owner of a managed object is permitted to write to it.
129 Though this is not strictly required for correctness, the performance of
130 the system wil be best if you ensure that only one thread at a time is
131 writing to objects with a particular owner.
132
133 Change notifications are delivered in a serialized fashion by a single
134 thread. Blocking this thread from a notification callback will stall
135 delivery of all notifications. It is therefore best practice to ensure
136 that you do not block or perform long-running operations from a
137 notification callback.
138
139 Key APIs and Interfaces
140 -----------------------
141
142 Basic Usage and Initialization
143 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144
145 The primary interface point into the framework is
146 opflex::ofcore::OFFramework. You can choose to instantiate your own copy
147 of the framework, or you can use the static default instance.
148
149 Before you can use the framework, you must initialize it by installing
150 your model metadata. The model metadata is accessible through the
151 generated model library. In this case, it assumes your model is called
152 "mymodel":
153
154 .. code:: cpp
155
156     #include <opflex/ofcore/OFFramework.h>
157     #include <mymodel/metadata/metadata.hpp>
158     // ...
159     using opflex::ofcore::OFFramework;
160     OFFramework::defaultInstance().setModel(mymodel::getMetadata());
161
162 The other critical piece of information required for initialization is
163 the OpFlex identity information. The identity information is required in
164 order to successfully connect to OpFlex peers. In OpFlex, each component
165 has a unique name within its policy domain, and each policy domain is
166 identified by a globally unique domain name. You can set this identity
167 information by calling:
168
169 .. code:: cpp
170
171     OFFramework::defaultInstance()
172         .setOpflexIdentity("[component name]", "[unique domain]");
173
174 You can then start the framework simply by calling:
175
176 .. code:: cpp
177
178     OFFramework::defaultInstance().start();
179
180 Finally, you can add peers after the framework is started by calling the
181 ``opflex::ofcore::OFFramework::addPeer`` method:
182
183 .. code:: cpp
184
185     OFFramework::defaultInstance().addPeer("192.168.1.5", 1234);
186
187 When connecting to the peer, that peer may provide an additional list of
188 peers to connect to, which will be automatically added as peers. If the
189 peer does not include itself in the list, then the framework will
190 disconnect from that peer and add the peers in the list. In this way, it
191 is possible to automatically bootstrap the correct set of peers using a
192 known hostname or IP address or a known, fixed anycast IP address.
193
194 To cleanly shut down, you can call:
195
196 .. code:: cpp
197
198     OFFramework::defaultInstance().stop();
199
200 Working with Data in the Tree
201 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
202
203 Reading from the Tree
204 ^^^^^^^^^^^^^^^^^^^^^
205
206 You can access data in the managed tree using the generated accessor
207 classes. The details of exactly which classes you’ll use will depend on
208 the model you’re using, but let’s assume that we have a simple model
209 called "simple" with the following classes:
210
211 -  root - The root node. The URI for the root node is "/"
212
213 -  foo - A policy object, and a child of root, with a scalar string
214    property called "bar", and a unsigned 64-bit integer property called
215    baz. The bar property is the naming property for foo. The URI for a
216    foo object would be "/foo/[value of bar]/"
217
218 -  fooref - A local-only child of root, with a reference to a foo, and a
219    scalar string property called "bar". The bar property is the naming
220    property for foo. The URI for a fooref object would be
221    "/fooref/[value of bar]/"
222
223 In this example, we’ll have a generated class for each of the objects.
224 There are two main ways to get access to an object in the tree.
225
226 First, we can get instantiate an accessor class to any node in the tree
227 by calling one of its static resolve functions. The resolve functions
228 can take either an already-built URI that represents the object, or you
229 can call the version that will locate the object by its naming
230 properties.
231
232 Second, we can access the object also from its parent object using the
233 appropriate child resolver member functions.
234
235 However we read it, the object we get back is an immutable view into the
236 object it references. The properties set locally on that object will not
237 change even though the underlying object may have been updated in the
238 store. Note, however, that its children can change between when you
239 first retrieve the object and when you resolve any children.
240
241 Another thing that is critical to note again is that when you attempt to
242 resolve an object, you can get back nothing, even if the object actually
243 does exist on another OpFlex node. You must ensure that some object in
244 the managed object database references the remote managed object you
245 want before it will be visible to you.
246
247 To get access to the root node using the default framework instance, we
248 can simply call:
249
250 .. code:: cpp
251
252     using boost::shared_ptr;
253     using boost::optional;
254     optional<shared_ptr<simple::root> > r(simple::root::resolve());
255
256 Note that whenever we can a resolve function, we get back our data in
257 the form of an optional shared pointer to the object instance. If the
258 node does not exist, then the optional will be set to boost::none. Note
259 that if you dereference an optional that has not been set, you’ll
260 trigger an assert, so you must check the return as follows:
261
262 .. code:: cpp
263
264     if (!r) {
265        // handle missing object
266     }
267
268 Now let’s get a child node of the root in three different ways:
269
270 .. code:: cpp
271
272     // Get foo1 by constructing its URI from the root
273     optional<shared_ptr<simple::foo> > foo1(simple::foo::resolve("test"));
274     // get foo1 by constructing its URI relative to its parent
275     foo1 = r.get()->resolveFoo("test");
276     // get foo1 by manually building its URI
277     foo1 = simple::foo::resolve(opflex::modb::URIBuilder()
278                                    .addElement("foo")
279                                    .addElement("test")
280                                    .build());
281
282 All three of these calls will give us the same object, which is the
283 "foo" object located at "/foo/test/".
284
285 The foo class has a single string property called "bar". We can easily
286 access it as follows:
287
288 .. code:: cpp
289
290     const std::string& barv = foo1.getBar();
291
292 Writing to the Tree
293 ^^^^^^^^^^^^^^^^^^^
294
295 Writing to the tree is nearly as easy as reading from it. The key
296 concept to understand is the mutator object. If you want to make changes
297 to the tree, you must allocate a mutator object. The mutator will
298 register itself in some thread-local storage in the framework instance
299 you’re using. The mutator is specific to a single "owner" for the data,
300 so you can only make changes to data associated with that owner.
301
302 Whenever you modify one of the accessor classes, the change is actually
303 forwarded to the currently-active mutator. You won’t see any of the
304 changes you make until you call the commit member function on the
305 mutator. When you do that, all the changes you made are written into the
306 store.
307
308 Once the changes are written into the store, you will need to call the
309 appropriate resolve function again to see the changes.
310
311 Allocating a mutator is simple. To create a mutator for the default
312 framework instance associated with the owner "owner1", just allocate the
313 mutator on the stack. Be sure to call commit() before it goes out of
314 scope or you’ll lose your changes.
315
316 .. code:: cpp
317
318     {
319         opflex::modb::Mutator mutator("owner1");
320         // make changes here
321         mutator.commit();
322     }
323
324 Note that if an exception is thrown while making changes but before
325 committing, the mutator will go out of scope and the changes will be
326 discarded.
327
328 To create a new node, you must call the appropriate add[Child] member
329 function on its parent. This function takes parameters for each of the
330 naming properties for the object:
331
332 .. code:: cpp
333
334     shared_ptr<simple::foo> newfoo(root->addFoo("test"));
335
336 This will return a shared pointer to a new foo object that has been
337 registered in the active mutator but not yet committed. The "bar" naming
338 property will be set automatically, but if you want to set the "baz"
339 property now, you can do so by calling:
340
341 .. code:: cpp
342
343     newfoo->setBaz(42);
344
345 Note that creating the root node requires a call to the special static
346 class method createRootElement:
347
348 .. code:: cpp
349
350     shared_ptr<simple::root> newroot(simple::root::createRootElement());
351
352 Here’s a complete example that ties this all together:
353
354 .. code:: cpp
355
356     {
357         opflex::modb::Mutator mutator("owner1");
358         shared_ptr<simple::root> newroot(simple::root::createRootElement());
359         shared_ptr<simple::root> newfoo(newroot->addFoo("test"));
360         newfoo->setBaz(42);
361
362         mutator.commit();
363     }
364
365 Update Notifications
366 ~~~~~~~~~~~~~~~~~~~~
367
368 When using the OpFlex framework, you’re likely to find that most of your
369 time is spend responding to changes in the managed object database. To
370 get these notifications, you’re going to need to register some number of
371 listeners.
372
373 You can register an object listener to see all changes related to a
374 particular class by calling a static function for that class. You’ll
375 then get notifications whenever any object in that class is added,
376 updated, or deleted. The listener should queue a task to read the new
377 state and perform appropriate processing. If this function blocks or
378 peforms a long-running operation, then the dispatching of update
379 notifications will be stalled, but there will not be any other
380 deleterious effects.
381
382 If multiple changes happen to the same URI, then at least one
383 notification will be delivered but some events may be consolidated.
384
385 The update you get will tell you the URI and the Class ID of the changed
386 object. The class ID is a unique ID for each class. When you get the
387 update, you’ll need to call the appropriate resolve function to retrieve
388 the new value.
389
390 You’ll need to create your own object listener derived from
391 opflex::modb::ObjectListener:
392
393 .. code:: cpp
394
395     class MyListener : public ObjectListener {
396     public:
397         MyListener() { }
398         virtual void objectUpdated(class_id_t class_id, const URI& uri) {
399             // Your handler here
400         }
401     };
402
403 To register your listener with the default framework instance, just call
404 the appropriate class static method:
405
406 .. code:: cpp
407
408     MyListener listener;
409     simple::foo::registerListener(&listener);
410     // main loop
411     simple::foo::unregisterListener(&listener);
412
413 The listener will now recieve notifications whenever any foo or any
414 children of any foo object changes.
415
416 Note that you must ensure that you unregister your listeners before
417 deallocating them.
418
419 API Reference Documentation
420 ---------------------------
421
422 Complete API documentation can be found through doxygen here:
423 https://jenkins.opendaylight.org/opflex/job/opflex-merge/ws/libopflex/doc/html/index.html
424