Support for ietf-interfaces statistics
[sweetcomb.git] / src / gnmi / gnmiserver.cpp
1 /*
2  * Copyright (c) 2019 PANTHEON.tech.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "gnmiserver.h"
18 #include "log.h"
19 #include "xml2json.h"
20
21 #include <iostream>
22 #include <exception>
23 #include <chrono>
24 #include <ctime>
25 #include <ratio>
26 #include <list>
27
28 #include <sysrepo.h>
29 #include <sysrepo/xpath.h>
30
31 gNMIServer::gNMIServer(SysrepoAPI& sysrepo)
32     : sysrepo(sysrepo)
33 {
34 }
35
36 grpc::Status gNMIServer::Capabilities(grpc::ServerContext* context,
37                                       const gnmi::CapabilityRequest* request,
38                                       gnmi::CapabilityResponse* reply)
39 {
40     DEBUG("Capabilities message");
41
42     if (0 < request->extension_size()) {
43         //TODO;
44     }
45
46     sysrepo.createSession();
47     const auto &ss = sysrepo.getSchemas();
48
49     for (const auto schema : ss) {
50         auto model = reply->add_supported_models();
51         model->set_name(schema.moduleName);
52         model->set_version(schema.revision);
53     }
54
55     reply->add_supported_encodings(gnmi::Encoding::ASCII);
56
57     //FIXME: I`m not sure, if this is a correct version of gnmi.
58     reply->set_gnmi_version(std::to_string(gnmi::kGnmiServiceFieldNumber));
59
60     sysrepo.closeSession();
61
62     return Status::OK;
63 }
64
65 grpc::Status gNMIServer::Get(grpc::ServerContext* context,
66                              const gnmi::GetRequest* request,
67                              gnmi::GetResponse* reply)
68 {
69     DEBUG("Get message");
70
71     std::cout << gnmi::GetRequest_DataType_Name(request->type()) << std::endl;
72
73     try {
74         sysrepo.createSession();
75
76         auto &prefix = request->prefix();
77         dataPrefix = convertToXPath(prefix);
78         DEBUG("Prefix: %s", dataPrefix.c_str());
79
80         DEBUG("Data type: %s",
81               request->DataType_Name(request->type()).c_str());
82         DEBUG("Encoding: %s", gnmi::Encoding_Name(request->encoding()).c_str());
83
84         for (const auto &model : request->use_models()) {
85             DEBUG("Model, name: %s, organization: %s, version: %s",
86                   model.name().c_str(), model.organization().c_str(),
87                   model.version().c_str());
88         }
89
90 //             for (const auto &extension : request->extension()) {
91 //                 DEBUG(ex);
92 //             }
93
94         if (0 < request->path().size()) {
95             for (const auto &path : request->path()) {
96                 std::string strPath = convertToXPath(path);
97                 vData.clean();
98                 vData.setXPath(dataPrefix + strPath);
99                 sysrepo.addData(vData);
100                 sysrepo.getItemMessage();
101
102                 auto notification = reply->add_notification();
103
104                 notification->set_timestamp(getTimeNanosec());
105                 auto elem = notification->mutable_prefix();
106                 xpathTogNMIEl(dataPrefix, *elem);
107
108                 const auto &oData = sysrepo.getOutputData();
109
110                 for (const auto &data : oData) {
111                     auto nupdate = notification->add_update();
112                     xpathTogNMIEl(data.getXPath(), *nupdate->mutable_path());
113                     auto uval = nupdate->mutable_val();
114                     uval->set_string_val(data.getStr());
115                 }
116             }
117         }
118
119         sysrepo.cleanData();
120         sysrepo.closeSession();
121     } catch (...) {
122         sysrepo.cleanData();
123         sysrepo.closeSession();
124         return Status::CANCELLED;
125     }
126
127     return Status::OK;
128 }
129
130 //TODO: Need handle INVALID operation, somehow, but how????
131 grpc::Status gNMIServer::Set(grpc::ServerContext* context,
132                              const gnmi::SetRequest* request,
133                              gnmi::SetResponse* reply)
134 {
135     DEBUG("Set message");
136     std::string path;
137
138     reply->set_timestamp(getTimeNanosec());
139
140     auto prefix = reply->mutable_prefix();
141     prefix->add_elem();
142
143     try {
144         sysrepo.createSession(SR_DS_RUNNING);
145
146         dataPrefix = convertToXPath(request->prefix());
147
148         if (0 < request->delete__size()) {
149             for (const auto &del : request->delete_()) {
150                 vData.clean();
151                 path = convertToXPath(del);
152                 vData.setXPath(dataPrefix + path);
153                 DEBUG("Delete: %s", vData.getXPath().c_str());
154                 sysrepo.addData(vData);
155             }
156             sysrepo.setItemMessage();
157
158             const auto &oData = sysrepo.getOutputData();
159             for (const auto &data : oData) {
160                 auto updateResult = reply->add_response();
161                 updateResult->set_op(gnmi::UpdateResult::DELETE);
162                 xpathTogNMIEl(data.getXPath(), *updateResult->mutable_path());
163             }
164
165             sysrepo.cleanData();
166         }
167
168         if (0 < request->replace_size()) {
169             for (const auto &replace : request->replace()) {
170                 vData.clean();
171                 handleUpdateMessage(replace);
172                 DEBUG("Replace : %s", vData.getXPath().c_str());
173                 sysrepo.addData(vData);
174             }
175             sysrepo.setItemMessage();
176
177             const auto &oData = sysrepo.getOutputData();
178             for (const auto &data : oData) {
179                 auto updateResult = reply->add_response();
180                 updateResult->set_op(gnmi::UpdateResult::REPLACE);
181                 xpathTogNMIEl(data.getXPath(), *updateResult->mutable_path());
182             }
183
184             sysrepo.cleanData();
185         }
186
187         if (0 < request->update_size()) {
188             for (const auto &update : request->update()) {
189                 vData.clean();
190                 handleUpdateMessage(update);
191                 DEBUG("Update: %s", vData.getXPath().c_str());
192 //                 sysrepo.addData(vData);
193                 XML2JSON json;
194                 json.setData(vData.getStr());
195                 json.setPrefix(vData.getXPath());
196                 auto &gdatas = json.getgNMIData();
197                 for (const auto &gdata : gdatas) {
198                     DEBUG("Data xpath: %s, data: %s", gdata.getXPath().c_str(),
199                           gdata.getStr().c_str());
200                     sysrepo.addData(gdata);
201                 }
202 //                 DEBUG("%s", json.getXML().c_str());
203             }
204             sysrepo.setItemMessage();
205
206             const auto &oData = sysrepo.getOutputData();
207             for (const auto &data : oData) {
208                 auto updateResult = reply->add_response();
209                 updateResult->set_op(gnmi::UpdateResult::UPDATE);
210                 xpathTogNMIEl(data.getXPath(), *updateResult->mutable_path());
211             }
212
213             sysrepo.cleanData();
214         }
215
216         //TODO: Need some special handling for sysrepo
217         sysrepo.commit();
218         sysrepo.closeSession();
219     } catch (...) {
220         sysrepo.cleanData();
221         sysrepo.closeSession();
222         return Status::CANCELLED;
223     }
224
225     return Status::OK;
226 }
227
228 grpc::Status gNMIServer::Subscribe(grpc::ServerContext* context,
229                                    ServerReaderWriter<gnmi::SubscribeResponse,
230                                    gnmi::SubscribeRequest>* stream)
231 {
232     DEBUG("Subscribe message");
233
234     gnmi::SubscribeRequest request;
235
236     try {
237         sysrepo.registerReciver<gNMIServer>(this);
238         sysrepo.registerStream(stream);
239
240         sysrepo.createSession(SR_DS_RUNNING);
241         while (stream->Read(&request)) {
242             switch (request.request_case()) {
243                 case gnmi::SubscribeRequest::kAliases:
244                     break;
245
246                 case gnmi::SubscribeRequest::kPoll:
247                     break;
248
249                 case gnmi::SubscribeRequest::kSubscribe:
250                     subscibeList(request.subscribe());
251                     break;
252
253                 case gnmi::SubscribeRequest::REQUEST_NOT_SET:
254                     break;
255
256                 default:
257                     DEBUG("Unknown request case.");
258                     break;
259             }
260         }
261     } catch (...) {
262         sysrepo.cleanData();
263         sysrepo.closeSession();
264         return Status::CANCELLED;
265     }
266
267     sysrepo.cleanData();
268     sysrepo.closeSession();
269     DEBUG("Subscribe message end");
270
271     return Status::OK;
272 }
273
274 void gNMIServer::subscibeList(const gnmi::SubscriptionList& slist)
275 {
276     std::string prefix = convertToXPath(slist.prefix());
277
278     if (0 < slist.subscription_size()) {
279         for (const auto &sub : slist.subscription()) {
280             vData.clean();
281             vData.setXPath(prefix + convertToXPath(sub.path()));
282             //TODO: Call sysrepo event
283
284             DEBUG("Subs Mode: %s",
285                   gnmi::SubscriptionMode_Name(sub.mode()).c_str());
286             DEBUG("Subs xpath: %s", vData.getXPath().c_str());
287             sysrepo.addData(vData);
288             sysrepo.eventSubscribeMessage();
289         }
290     }
291
292     DEBUG("Subscribe list mode: %s", slist.Mode_Name(slist.mode()).c_str());
293
294     if (0 < slist.use_models_size()) {
295         for (const auto &umod : slist.use_models()) {
296             DEBUG("Name: %s, Organization: %s, Version: %s",
297                   umod.name().c_str(), umod.organization().c_str(),
298                   umod.version().c_str());
299         }
300     }
301
302     DEBUG("Encoding: %s", gnmi::Encoding_Name(slist.encoding()).c_str());
303
304     DEBUG("Update only: %d", slist.updates_only());
305 }
306
307 void gNMIServer::receiveWriteEvent(SysrepoAPI* psender)
308 {
309     gnmi::SubscribeResponse response;
310     ServerReaderWriter<gnmi::SubscribeResponse, gnmi::SubscribeRequest>* stream;
311
312     stream = (ServerReaderWriter<gnmi::SubscribeResponse,
313               gnmi::SubscribeRequest>*) psender->getStream();
314
315     auto sData = psender->getOutputData();
316     auto update = response.mutable_update();
317
318     update->set_timestamp(getTimeNanosec());
319     auto elem = update->mutable_prefix();
320
321     xpathTogNMIEl("/", *elem);
322     for (const auto &data : sData) {
323         DEBUG("XPath: %s, value: %s",
324               data.getXPath().c_str(), data.getStr().c_str());
325         auto nupdate = update->add_update();
326         xpathTogNMIEl(data.getXPath(), *nupdate->mutable_path());
327         auto val = nupdate->mutable_val();
328         val->set_string_val(data.getStr());
329     }
330
331     psender->cleanData();
332     stream->Write(response);
333 }
334
335 void gNMIServer::parsePathMsg(const gnmi::Path& path)
336 {
337     if (0 >= path.elem_size()) {
338         DEBUG("Path is empty");
339         return;
340     }
341
342 }
343
344 std::string gNMIServer::convertToXPath(const gnmi::Path& path)
345 {
346     std::string str = "/";
347
348     if (0 < path.elem_size()) {
349         for (const auto &elm : path.elem()) {
350             str += elm.name();
351
352             for (const auto &el : elm.key()) {
353                 str += "[" + el.first + "='" + el.second + "']";
354             }
355
356             str += "/";
357         }
358     }
359     str.pop_back();
360
361     return str;
362 }
363
364 void gNMIServer::printPath(const gnmi::Path& path)
365 {
366     if (0 < path.element().size()) {
367         for (const auto &element : path.element()) {
368             DEBUG("Element: %s", element.c_str());
369         }
370     }
371
372     if (0 < path.elem_size()) {
373         for (const auto &elm : path.elem()) {
374             DEBUG("Elm name: %ss", elm.name().c_str());
375             for (const auto &el : elm.key()) {
376                 DEBUG("El key: %s, val: %s", el.first.c_str(),
377                       el.second.c_str());
378             }
379         }
380     }
381 }
382
383 void gNMIServer::handleUpdateMessage(const gnmi::Update& msg)
384 {
385     vData.setXPath(dataPrefix + convertToXPath(msg.path()));
386
387     if (msg.has_val()) {
388         handleTypeValueMsg(msg.val());
389     }
390 }
391
392 void gNMIServer::handleTypeValueMsg(const gnmi::TypedValue& msg)
393 {
394     DEBUG("Val case: %d", msg.value_case());
395
396     switch (msg.value_case()) {
397         case gnmi::TypedValue::ValueCase::kStringVal:
398             vData.setValue(msg.string_val());
399             break;
400
401         case gnmi::TypedValue::ValueCase::kIntVal:
402             break;
403
404         case gnmi::TypedValue::ValueCase::kUintVal:
405             break;
406
407         case gnmi::TypedValue::ValueCase::kBoolVal:
408             break;
409
410         case gnmi::TypedValue::ValueCase::kBytesVal:
411             break;
412
413         case gnmi::TypedValue::ValueCase::kFloatVal:
414             break;
415
416         case gnmi::TypedValue::ValueCase::kDecimalVal:
417             break;
418
419         case gnmi::TypedValue::ValueCase::kLeaflistVal:
420             break;
421
422         case gnmi::TypedValue::ValueCase::kAnyVal:
423             break;
424
425         case gnmi::TypedValue::ValueCase::kJsonVal:
426             break;
427
428         case gnmi::TypedValue::ValueCase::kJsonIetfVal:
429             vData.setValue(msg.json_ietf_val());
430             break;
431
432         case gnmi::TypedValue::ValueCase::kAsciiVal:
433             break;
434
435         case gnmi::TypedValue::ValueCase::kProtoBytes:
436             break;
437
438         case gnmi::TypedValue::ValueCase::VALUE_NOT_SET:
439             break;
440
441         default:
442             DEBUG("Unknown type.");
443             break;
444     }
445 }
446
447 void gNMIServer::xpathTogNMIEl(const std::string& str, gnmi::Path& path)
448 {
449     sr_xpath_ctx_t state;
450     std::string tmp(str);
451     const char *xpath = tmp.c_str();
452     char *name;
453
454     if ((name = sr_xpath_next_node((char *)xpath, &state)) == NULL) {
455         DEBUG("Empty XPATH, xpath: %s", xpath);
456         return;
457     }
458
459     do {
460         auto pathEl = path.add_elem();
461         pathEl->set_name(name);
462
463         const char *key_name;
464         while ((key_name = sr_xpath_next_key_name(NULL, &state)) != NULL) {
465             std::string key(key_name);
466             const char *key_value = sr_xpath_next_key_value(NULL, &state);
467             (*pathEl->mutable_key())[key] = key_value;
468         }
469     } while ((name = sr_xpath_next_node(NULL, &state)) != NULL);
470
471     sr_xpath_recover(&state);
472 }
473
474
475 uint64_t gNMIServer::getTimeNanosec()
476 {
477     using namespace std::chrono;
478
479     std::uint64_t tm = high_resolution_clock::now().time_since_epoch() /
480                                                                 nanoseconds(1);
481
482     return tm;
483 }