Fledge
An open source edge computing platform for industrial users
python_plugin_common_interface.h
1 #ifndef _PYTHON_PLUGIN_BASE_INTERFACE_H
2 #define _PYTHON_PLUGIN_BASE_INTERFACE_H
3 /*
4  * Fledge common plugin interface
5  *
6  * Copyright (c) 2019 Dianomic Systems
7  *
8  * Released under the Apache 2.0 Licence
9  *
10  * Author: Massimiliano Pinto, Amandeep Singh Arora
11  */
12 
13 #include <cctype>
14 #include <plugin_manager.h>
15 
16 #define SHIM_SCRIPT_REL_PATH "/python/fledge/plugins/common/shim/"
17 #define SHIM_SCRIPT_POSTFIX "_shim"
18 
19 using namespace std;
20 
28 {
29  public:
30  PythonModule(PyObject* module,
31  bool init,
32  string name,
33  string type,
34  PyThreadState* state) :
35  m_module(module),
36  m_init(init),
37  m_name(name),
38  m_type(type),
39  m_tState(state)
40  {
41  };
42 
43  ~PythonModule()
44  {
45  // Destroy loaded Python module
46  Py_CLEAR(m_module);
47  m_module = NULL;
48  };
49 
50  void setCategoryName(string category)
51  {
52  m_categoryName = category;
53  };
54 
55  string& getCategoryName()
56  {
57  return m_categoryName;
58  };
59 
60  public:
61  PyObject* m_module;
62  bool m_init;
63  string m_name;
64  string m_type;
65  PyThreadState* m_tState;
66  string m_categoryName;
67 };
68 
69 extern "C" {
70 // This is the map of Python object initialised in each
71 // South, Notification, Filter plugin interfaces
72 static map<string, PythonModule*> *pythonModules = new map<string, PythonModule*>();
73 // Map of PLUGIN_HANDLE objects, updated by plugin_init calls
74 static map<PLUGIN_HANDLE, PythonModule*> *pythonHandles = new map<PLUGIN_HANDLE, PythonModule*>();
75 
76 // Global variable gPluginName set by PluginInterfaceInit:
77 // it has a different memory address when set/read by
78 // PluginInterfaceInit in South, Filter or Notification
79 // Only used in plugin_info_fn calls
80 static string gPluginName;
81 
82 // Common methods to all plugin interfaces
83 static PLUGIN_INFORMATION *plugin_info_fn();
84 static PLUGIN_HANDLE plugin_init_fn(ConfigCategory *);
85 static void plugin_reconfigure_fn(PLUGIN_HANDLE*, const std::string&);
86 static void plugin_shutdown_fn(PLUGIN_HANDLE);
87 
88 static void logErrorMessage();
89 static bool numpyImportError = false;
90 
98 void PluginInterfaceCleanup(const string& pluginName)
99 {
100  bool removePython = false;
101 
102  if (!pythonModules)
103  {
104  Logger::getLogger()->error("pythonModules map is NULL "
105  "in PluginInterfaceCleanup, plugin '%s'",
106  pluginName.c_str());
107 
108  return;
109  }
110 
111  // Acquire GIL
112  PyGILState_STATE state = PyGILState_Ensure();
113 
114  // Look for Python module, pluginName is the key
115  auto it = pythonModules->find(pluginName);
116  if (it != pythonModules->end())
117  {
118  // Remove Python 3.x environment?
119  removePython = it->second->m_init;
120 
121  // Remove this element
122  pythonModules->erase(it);
123  }
124 
125  // Look for Python module handle
126  for (auto h = pythonHandles->begin();
127  h != pythonHandles->end(); )
128  {
129  // Compare pluginName with m_name
130  if (h->second->m_name.compare(pluginName) == 0)
131  {
132  // Remove PythonModule object
133  if (h->second->m_module)
134  {
135  Py_CLEAR(h->second->m_module);
136  h->second->m_module = NULL;
137  }
138 
139  // Remove PythonModule
140  delete h->second;
141  h->second = NULL;
142 
143  // Remove this element
144  h = pythonHandles->erase(h);
145  }
146  else
147  {
148  ++h;
149  }
150  }
151 
152  // Remove PythonModule object
153  if (it->second &&
154  it->second->m_module)
155  {
156  Py_CLEAR(it->second->m_module);
157  it->second->m_module = NULL;
158  }
159 
160  // Remove all maps if empty
161  if (pythonModules->size() == 0)
162  {
163  // Remove map object
164  delete pythonModules;
165  }
166  if (pythonHandles->size() == 0)
167  {
168  // Remove map object
169  delete pythonHandles;
170  }
171 
172  if (removePython)
173  {
174  Logger::getLogger()->debug("Removing Python interpreter "
175  "started by plugin '%s'",
176  pluginName.c_str());
177 
178  // Cleanup Python 3.5
179  Py_Finalize();
180  }
181  else
182  {
183  PyGILState_Release(state);
184  }
185 
186  Logger::getLogger()->debug("PluginInterfaceCleanup succesfully "
187  "called for plugin '%s'",
188  pluginName.c_str());
189 }
190 
195 static void* PluginInterfaceGetInfo()
196 {
197  return (void *) plugin_info_fn;
198 }
199 
206 void set_loglevel_in_python_module(PyObject *python_module, string s)
207 {
208  string& _loglevel = Logger::getLogger()->getMinLevel();
209  for (auto & c: _loglevel) c = toupper(c);
210  const char *loglevel = _loglevel.c_str();
211 
212  PyObject* mod = python_module;
213  if (mod != NULL)
214  {
215  PyObject* loggerObj = PyObject_GetAttrString(mod, "_LOGGER");
216  if (loggerObj != NULL)
217  {
218  PyObject* method = PyObject_GetAttrString(loggerObj, "setLevel");
219  if (method != NULL)
220  {
221  PyObject *args = PyTuple_New(1);
222  PyObject *pValue = Py_BuildValue("s", loglevel);
223  PyTuple_SetItem(args, 0, pValue);
224  PyObject* retVal = PyObject_Call(method, args, NULL);
225 
226  Py_CLEAR(args);
227  Py_CLEAR(method);
228  Py_CLEAR(loggerObj);
229  if (retVal != NULL)
230  {
231  Logger::getLogger()->debug("%s: %s: _LOGGER.setLevel(%s) done successfully", __FUNCTION__, s.c_str(), loglevel);
232  }
233  else
234  {
235  Logger::getLogger()->warn("%s: _LOGGER.setLevel(%s) failed", __FUNCTION__, loglevel);
236  if (PyErr_Occurred())
237  {
238  logErrorMessage();
239  return;
240  }
241  }
242  }
243  else
244  {
245  Logger::getLogger()->warn("%s: Method 'setLevel' not found", __FUNCTION__);
246  Py_CLEAR(loggerObj);
247  }
248  }
249  else
250  {
251  Logger::getLogger()->warn("%s: Object '_LOGGER' not found in python module", __FUNCTION__);
252  }
253  }
254  else
255  Logger::getLogger()->warn("%s: module is NULL", __FUNCTION__);
256 
257  PyErr_Clear();
258 }
259 
263 const char *json_dumps(PyObject *json_dict)
264 {
265  PyObject *rval;
266  PyObject *mod, *method;
267 
268  PyGILState_STATE state = PyGILState_Ensure();
269  if ((mod = PyImport_ImportModule("json")) != NULL)
270  {
271  if ((method = PyObject_GetAttrString(mod, "dumps")) != NULL)
272  {
273  PyObject *args = PyTuple_New(1);
274  PyObject *pValue = Py_BuildValue("O", json_dict);
275  PyTuple_SetItem(args, 0, pValue);
276 
277  rval = PyObject_Call(method, args, NULL);
278  Py_CLEAR(args);
279  Py_CLEAR(method);
280  Py_CLEAR(mod);
281 
282  if (rval == NULL)
283  {
284  if (PyErr_Occurred())
285  {
286  logErrorMessage();
287  return NULL;
288  }
289  }
290  else
291  Logger::getLogger()->info("%s:%d, rval type=%s", __FUNCTION__, __LINE__, (Py_TYPE(rval))->tp_name);
292 
293  }
294  else
295  {
296  Logger::getLogger()->fatal("Method 'dumps' not found");
297  Py_CLEAR(mod);
298  }
299  // Remove references
300 
301  }
302  else
303  {
304  Logger::getLogger()->fatal("Failed to import module");
305  }
306 
307  // Reset error
308  PyErr_Clear();
309 
310  PyGILState_Release(state);
311 
312  const char *retVal = PyUnicode_AsUTF8(rval);
313  Logger::getLogger()->debug("%s: retVal=%s", __FUNCTION__, retVal);
314 
315  return retVal;
316 }
317 
318 
322 PyObject *json_loads(const char *json_str)
323 {
324 PyObject *rval;
325 PyObject *mod, *method;
326 
327  PyGILState_STATE state = PyGILState_Ensure();
328  if ((mod = PyImport_ImportModule("json")) != NULL)
329  {
330  if ((method = PyObject_GetAttrString(mod, "loads")) != NULL)
331  {
332  PyObject *args = PyTuple_New(1);
333  PyObject *pValue = Py_BuildValue("s", json_str);
334  PyTuple_SetItem(args, 0, pValue);
335 
336  Logger::getLogger()->debug("%s:%d: method=%p, args=%p, pValue=%p", __FUNCTION__, __LINE__, method, args, pValue);
337  rval = PyObject_Call(method, args, NULL);
338  Py_CLEAR(args);
339  Py_CLEAR(method);
340  Py_CLEAR(mod);
341 
342  if (rval == NULL)
343  {
344  if (PyErr_Occurred())
345  {
346  logErrorMessage();
347  return NULL;
348  }
349  }
350  else
351  Logger::getLogger()->debug("%s:%d, rval type=%s", __FUNCTION__, __LINE__, (Py_TYPE(rval))->tp_name);
352 
353  }
354  else
355  {
356  Logger::getLogger()->fatal("Method 'loads' not found");
357  Py_CLEAR(mod);
358  }
359  }
360  else
361  {
362  Logger::getLogger()->fatal("Failed to import module");
363  }
364 
365  // Reset error
366  PyErr_Clear();
367 
368  PyGILState_Release(state);
369 
370  return rval;
371 }
372 
373 
381 static PLUGIN_INFORMATION *Py2C_PluginInfo(PyObject* pyRetVal)
382 {
383  // Create returnable PLUGIN_INFORMATION structure
385  info->options = 0;
386 
387  // these are borrowed references returned by PyDict_Next
388  PyObject *dKey, *dValue;
389  Py_ssize_t dPos = 0;
390 
391  PyObject* objectsRepresentation = PyObject_Repr(pyRetVal);
392  const char* s = PyUnicode_AsUTF8(objectsRepresentation);
393  Logger::getLogger()->debug("Py2C_PluginInfo(): plugin_info returned: %s", s);
394  Py_CLEAR(objectsRepresentation);
395 
396  // dKey and dValue are borrowed references
397  while (PyDict_Next(pyRetVal, &dPos, &dKey, &dValue))
398  {
399  const char* ckey = PyUnicode_AsUTF8(dKey);
400  const char* cval = PyUnicode_AsUTF8(dValue);
401  Logger::getLogger()->debug("%s:%d, key=%s, value=%s, dValue type=%s", __FUNCTION__, __LINE__, ckey, cval, (Py_TYPE(dValue))->tp_name);
402 
403  char *valStr = NULL;
404  if (!PyDict_Check(dValue))
405  {
406  valStr = new char [string(cval).length()+1];
407  std::strcpy (valStr, cval);
408  Logger::getLogger()->debug("%s:%d, key=%s, value=%s, valStr=%s", __FUNCTION__, __LINE__, ckey, cval, valStr);
409  }
410 
411  if(!strcmp(ckey, "name"))
412  {
413  info->name = valStr;
414  }
415  else if(!strcmp(ckey, "version"))
416  {
417  info->version = valStr;
418  }
419  else if(!strcmp(ckey, "mode"))
420  {
421  // Need to also handle mode values of the form "poll|control"
422  stringstream ss(valStr);
423  string s;
424 
425  info->options = 0;
426 
427  // Tokenizing w.r.t. pipe '|'
428  while(getline(ss, s, '|'))
429  {
430  Logger::getLogger()->debug("%s: mode: Found token %s", __FUNCTION__, s.c_str());
431  if (s.compare("async")==0)
432  {
433  info->options |= SP_ASYNC;
434  }
435  else if (s.compare("control")==0)
436  {
437  info->options |= SP_CONTROL;
438  }
439  else if (s.compare("poll")==0)
440  {
441  // Nothing to set
442  }
443  else if (s.compare("none")==0)
444  {
445  // Ignore
446  }
447  else
448  Logger::getLogger()->warn("%s: mode: Unknown token/value %s", __FUNCTION__, s.c_str());
449  }
450 
451  delete[] valStr;
452  }
453  else if(!strcmp(ckey, "type"))
454  {
455  info->type = valStr;
456  }
457  else if(!strcmp(ckey, "interface"))
458  {
459  info->interface = valStr;
460  }
461  else if(!strcmp(ckey, "config"))
462  {
463  // if 'config' value is of dict type, convert it to string
464  if (strcmp((Py_TYPE(dValue))->tp_name, "dict")==0)
465  {
466  PyObject* objectsRepresentation = PyObject_Repr(dValue);
467  const char* s = PyUnicode_AsUTF8(objectsRepresentation);
468  Logger::getLogger()->debug("Py2C_PluginInfo(): INPUT: config value=%s", s);
469  Py_CLEAR(objectsRepresentation);
470 
471  info->config = json_dumps(dValue);
472  Logger::getLogger()->info("Py2C_PluginInfo(): OUTPUT: config value=%s", info->config);
473  }
474  else
475  info->config = valStr;
476  }
477  else
478  Logger::getLogger()->info("%s:%d: Unexpected key %s", __FUNCTION__, __LINE__, ckey);
479  }
480 
481  return info;
482 }
483 
487 static PLUGIN_INFORMATION *plugin_info_fn()
488 {
489  if (!pythonModules)
490  {
491  Logger::getLogger()->error("pythonModules map is NULL "
492  "in plugin_info_fn, plugin '%s'",
493  gPluginName.c_str());
494  return NULL;
495  }
496 
497  // Look for Python module for gPluginName key
498  auto it = pythonModules->find(gPluginName);
499  if (it == pythonModules->end() ||
500  !it->second ||
501  !it->second->m_module)
502  {
503  Logger::getLogger()->fatal("plugin_handle: plugin_info(): "
504  "pModule is NULL for plugin '%s'",
505  gPluginName.c_str());
506  return NULL;
507  }
508  PyObject* pFunc;
509  PyGILState_STATE state = PyGILState_Ensure();
510 
511  // Fetch required method in loaded object
512  pFunc = PyObject_GetAttrString(it->second->m_module, "plugin_info");
513  if (!pFunc)
514  {
515  Logger::getLogger()->fatal("Cannot find method 'plugin_info' "
516  "in loaded python module '%s', m_module=%p",
517  gPluginName.c_str(), it->second->m_module);
518  }
519 
520  if (!pFunc || !PyCallable_Check(pFunc))
521  {
522  // Failure
523  if (PyErr_Occurred())
524  {
525  logErrorMessage();
526  }
527 
528  Logger::getLogger()->fatal("Cannot call method 'plugin_info' "
529  "in loaded python module '%s'",
530  gPluginName.c_str());
531  Py_CLEAR(pFunc);
532 
533  PyGILState_Release(state);
534  return NULL;
535  }
536 
537  // Call Python method passing an object
538  PyObject* pReturn = PyObject_CallFunction(pFunc, NULL);
539 
540  Py_CLEAR(pFunc);
541 
542  PLUGIN_INFORMATION *info = NULL;
543 
544  // Handle returned data
545  if (!pReturn)
546  {
547  Logger::getLogger()->error("Called python script method 'plugin_info' "
548  ": error while getting result object, plugin '%s'",
549  gPluginName.c_str());
550  logErrorMessage();
551  info = NULL;
552  }
553  else
554  {
555  // Parse plugin information
556  info = Py2C_PluginInfo(pReturn);
557 
558  // Remove pReturn object
559  Py_CLEAR(pReturn);
560  }
561 
562  if (info)
563  {
564  // bump interface version to atleast 2.x so that we are able to handle
565  // list of readings from python plugins in plugin_poll
566  if (info->interface[0] =='1' &&
567  info->interface[1] == '.')
568  {
569  Logger::getLogger()->info("plugin_handle: plugin_info(): "
570  "Updating interface version "
571  "from '%s' to '2.0.0', plugin '%s'",
572  info->interface,
573  gPluginName.c_str());
574  delete[] info->interface;
575  char *valStr = new char[6];
576  std::strcpy(valStr, "2.0.0");
577  info->interface = valStr;
578  }
579 
580  Logger::getLogger()->info("plugin_handle: plugin_info(): info={name=%s, "
581  "version=%s, options=%d, type=%s, interface=%s, config=%s}",
582  info->name,
583  info->version,
584  info->options,
585  info->type,
586  info->interface,
587  info->config);
588  }
589 
590  PyGILState_Release(state);
591 
592  return info;
593 }
594 
601 static PLUGIN_HANDLE plugin_init_fn(ConfigCategory *config)
602 {
603  // Get plugin name
604  string pName = config->getValue("plugin");
605 
606  if (!pythonModules)
607  {
608  Logger::getLogger()->error("pythonModules map is NULL "
609  "in plugin_init_fn, plugin '%s'",
610  pName.c_str());
611  return NULL;
612  }
613 
614  Logger::getLogger()->debug("plugin_handle: plugin_init(): "
615  "config->itemsToJSON()='%s'",
616  config->itemsToJSON().c_str());
617 
618  bool loadModule = false;
619  bool reloadModule = false;
620  bool pythonInitState = false;
621  string loadPluginType;
622 
623  PythonModule* module = NULL;
624  PyThreadState* newInterp = NULL;
625 
626  // Check wether plugin pName has been already loaded
627  for (auto h = pythonHandles->begin();
628  h != pythonHandles->end();
629  ++h)
630  {
631  if (h->second->m_name.compare(pName) == 0)
632  {
633  Logger::getLogger()->debug("%s_plugin_init_fn: already loaded "
634  "a plugin with name '%s'. Loading a new ",
635  h->second->m_type.c_str(),
636  pName.c_str());
637 
638  // Set Python library loaded state
639  pythonInitState = h->second->m_init;
640 
641  // Set plugin type
642  loadPluginType = h->second->m_type;
643 
644  // Set load indicator
645  loadModule = true;
646  }
647  }
648 
649  if (!loadModule)
650  {
651 
652  // Plugin name not previously loaded: check current Python module
653  // pName is the key
654  auto it = pythonModules->find(pName);
655  if (it == pythonModules->end())
656  {
657  Logger::getLogger()->debug("plugin_handle: plugin_init(): "
658  "pModule not found for plugin '%s': ",
659  pName.c_str());
660 
661  // Set plugin type
663  PLUGIN_HANDLE tmp = pMgr->findPluginByName(pName);
664  if (tmp)
665  {
666  PLUGIN_INFORMATION* pInfo = pMgr->getInfo(tmp);
667  if (pInfo)
668  {
669  loadPluginType = string(pInfo->type);
670  }
671  }
672 
673  // Set reload indicator
674  reloadModule = true;
675  }
676  else
677  {
678  if (it->second && it->second->m_module)
679  {
680  // Just use current loaded module: no load or re-load action
681  module = it->second;
682 
683  // Set Python library loaded state
684  pythonInitState = it->second->m_init;
685  }
686  else
687  {
688  Logger::getLogger()->fatal("plugin_handle: plugin_init(): "
689  "found pModule is NULL for plugin '%s': ",
690  pName.c_str());
691  return NULL;
692  }
693  }
694  }
695 
696  Logger::getLogger()->info("%s:%d: loadModule=%s, reloadModule=%s",
697  __FUNCTION__, __LINE__, loadModule?"TRUE":"FALSE", reloadModule?"TRUE":"FALSE");
698 
699  // Acquire GIL
700  PyGILState_STATE state = PyGILState_Ensure();
701 
702  // Import Python module using a new interpreter
703  if (loadModule || reloadModule)
704  {
705  string fledgePythonDir;
706 
707  string fledgeRootDir(getenv("FLEDGE_ROOT"));
708  fledgePythonDir = fledgeRootDir + "/python";
709 
710  int argc = 2;
711 
712  // Set Python path for embedded Python 3.x
713  // Get current sys.path - borrowed reference
714  PyObject* sysPath = PySys_GetObject((char *)"path");
715  PyList_Append(sysPath, PyUnicode_FromString((char *) fledgePythonDir.c_str()));
716 
717  // Set sys.argv for embedded Python 3.x
718  wchar_t* argv[argc];
719  argv[0] = Py_DecodeLocale("", NULL);
720  argv[1] = Py_DecodeLocale(pName.c_str(), NULL);
721  if (argc > 2)
722  {
723  argv[2] = Py_DecodeLocale(loadPluginType.c_str(), NULL);
724  }
725 
726  // Set script parameters
727  PySys_SetArgv(argc, argv);
728 
729  Logger::getLogger()->debug("%s_plugin_init_fn, %sloading plugin '%s', ",
730  loadPluginType.c_str(),
731  reloadModule ? "re-" : "",
732  pName.c_str());
733 
734  // Import Python script
735  PyObject *newObj = PyImport_ImportModule(pName.c_str());
736 
737  // Check result
738  if (newObj)
739  {
740  // Create a new PythonModule
741  PythonModule* newModule;
742  if ((newModule = new PythonModule(newObj,
743  pythonInitState,
744  pName,
745  loadPluginType,
746  NULL)) == NULL)
747  {
748  // Release lock
749  PyGILState_Release(state);
750 
751  Logger::getLogger()->fatal("plugin_handle: plugin_init(): "
752  "failed to create Python module "
753  "object, plugin '%s'",
754  pName.c_str());
755  return NULL;
756  }
757 
758  // Set module
759  module = newModule;
760  }
761  else
762  {
763  logErrorMessage();
764 
765  // Release lock
766  PyGILState_Release(state);
767 
768  Logger::getLogger()->fatal("plugin_handle: plugin_init(): "
769  "failed to import plugin '%s'",
770  pName.c_str());
771  return NULL;
772  }
773  }
774 
775  Logger::getLogger()->debug("%s_plugin_init_fn for '%s', pModule '%p', ",
776  loadPluginType.c_str(),
777  module->m_name.c_str(),
778  module->m_module);
779 
780  Logger::getLogger()->debug("%s:%d: calling set_loglevel_in_python_module(), loglevel=%s", __FUNCTION__, __LINE__, Logger::getLogger()->getMinLevel().c_str());
781  set_loglevel_in_python_module(module->m_module, module->m_name + " plugin_init");
782 
783  PyObject *config_dict = json_loads(config->itemsToJSON().c_str());
784 
785  // Call Python method passing an object
786  PyObject* pReturn = PyObject_CallMethod(module->m_module,
787  "plugin_init",
788  "O",
789  config_dict);
790 
791  Py_CLEAR(config_dict);
792 
793  // Handle returned data
794  if (!pReturn)
795  {
796  Logger::getLogger()->error("Called python script method plugin_init : "
797  "error while getting result object, plugin '%s'",
798  pName.c_str());
799  logErrorMessage();
800  }
801  else
802  {
803  Logger::getLogger()->debug("plugin_handle: plugin_init(): "
804  "got handle from python plugin='%p', *handle %p, plugin '%s'",
805  pReturn,
806  &pReturn,
807  pName.c_str());
808  }
809 
810  // Add the handle to handles map as key, PythonModule object as value
811  std::pair<std::map<PLUGIN_HANDLE, PythonModule*>::iterator, bool> ret;
812  if (pythonHandles)
813  {
814  // Add to handles map the PythonModule object
815  ret = pythonHandles->insert(pair<PLUGIN_HANDLE, PythonModule*>
816  ((PLUGIN_HANDLE)pReturn, module));
817 
818  if (ret.second)
819  {
820  Logger::getLogger()->debug("plugin_handle: plugin_init(): "
821  "handle %p of python plugin '%s' "
822  "added to pythonHandles map",
823  pReturn,
824  pName.c_str());
825  }
826  else
827  {
828  Logger::getLogger()->error("plugin_handle: plugin_init(): "
829  "failed to insert handle %p of "
830  "python plugin '%s' to pythonHandles map",
831  pReturn,
832  pName.c_str());
833 
834  Py_CLEAR(module->m_module);
835  module->m_module = NULL;
836  delete module;
837  module = NULL;
838 
839  Py_CLEAR(pReturn);
840  pReturn = NULL;
841  }
842  }
843 
844  // Release locks
845  if (newInterp)
846  {
847  PyEval_ReleaseThread(newInterp);
848  }
849  else
850  {
851  PyGILState_Release(state);
852  }
853 
854  return pReturn ? (PLUGIN_HANDLE) pReturn : NULL;
855 }
856 
863 static void plugin_reconfigure_fn(PLUGIN_HANDLE* handle,
864  const std::string& config)
865 {
866  Logger::getLogger()->debug("%s:%d: config=%s", __FUNCTION__, __LINE__, config.c_str());
867 
868  if (!handle)
869  {
870  Logger::getLogger()->fatal("plugin_handle: plugin_reconfigure(): "
871  "handle is NULL");
872  return;
873  }
874 
875  if (!pythonHandles)
876  {
877  Logger::getLogger()->error("pythonHandles map is NULL "
878  "in plugin_reconfigure, plugin handle '%p'",
879  handle);
880  return;
881  }
882 
883  // Look for Python module for handle key
884  auto it = pythonHandles->find(*handle);
885  if (it == pythonHandles->end() ||
886  !it->second ||
887  !it->second->m_module)
888  {
889  Logger::getLogger()->fatal("plugin_handle: plugin_reconfigure(): "
890  "pModule is NULL, plugin handle '%p'",
891  handle);
892  return;
893  }
894 
895  std::mutex mtx;
896  PyObject* pFunc;
897  lock_guard<mutex> guard(mtx);
898  PyGILState_STATE state = PyGILState_Ensure();
899 
900  Logger::getLogger()->debug("plugin_handle: plugin_reconfigure(): "
901  "pModule=%p, *handle=%p, plugin '%s'",
902  it->second->m_module,
903  *handle,
904  it->second->m_name.c_str());
905 
906  if(config.compare("logLevel") == 0)
907  {
908  Logger::getLogger()->debug("calling set_loglevel_in_python_module() for updating loglevel");
909  set_loglevel_in_python_module(it->second->m_module, it->second->m_name+" plugin_reconf");
910  PyGILState_Release(state);
911  return;
912  }
913 
914  // Fetch required method in loaded object
915  pFunc = PyObject_GetAttrString(it->second->m_module, "plugin_reconfigure");
916  if (!pFunc)
917  {
918  Logger::getLogger()->fatal("Cannot find method 'plugin_reconfigure' "
919  "in loaded python module '%s'",
920  it->second->m_name.c_str());
921  }
922 
923  if (!pFunc || !PyCallable_Check(pFunc))
924  {
925  // Failure
926  if (PyErr_Occurred())
927  {
928  logErrorMessage();
929  }
930 
931  Logger::getLogger()->fatal("Cannot call method plugin_reconfigure "
932  "in loaded python module '%s'",
933  it->second->m_name.c_str());
934  Py_CLEAR(pFunc);
935 
936  PyGILState_Release(state);
937  return;
938  }
939 
940  Logger::getLogger()->debug("plugin_reconfigure with %s", config.c_str());
941 
942  PyObject *new_config_dict = json_loads(config.c_str());
943 
944  // Call Python method passing an object and a C string
945  PyObject* pReturn = PyObject_CallFunction(pFunc,
946  "OO",
947  *handle,
948  new_config_dict);
949 
950  Py_CLEAR(pFunc);
951  Py_CLEAR(new_config_dict);
952 
953  // Handle returned data
954  if (!pReturn)
955  {
956  Logger::getLogger()->error("Called python script method plugin_reconfigure "
957  ": error while getting result object, plugin '%s'",
958  it->second->m_name.c_str());
959  logErrorMessage();
960  //*handle = NULL; // not sure if this should be treated as unrecoverable failure on python plugin side
961  }
962  else
963  {
964  // Save PythonModule
965  PythonModule* currentModule = it->second;
966 
967  Py_CLEAR(*handle);
968  *handle = pReturn;
969 
970  if (pythonHandles)
971  {
972  // Remove current handle from the pythonHandles map
973  pythonHandles->erase(it);
974 
975  // Add the handle to handles map as key, PythonModule object as value
976  std::pair<std::map<PLUGIN_HANDLE, PythonModule*>::iterator, bool> ret;
977  ret = pythonHandles->insert(pair<PLUGIN_HANDLE, PythonModule*>
978  ((PLUGIN_HANDLE)*handle, currentModule));
979 
980  Logger::getLogger()->debug("plugin_handle: plugin_reconfigure(): "
981  "updated handle %p of python plugin '%s'"
982  " in pythonHandles map",
983  *handle,
984  currentModule->m_name.c_str());
985  }
986  else
987  {
988  Logger::getLogger()->error("plugin_handle: plugin_reconfigure(): "
989  "failed to update handle %p of python plugin '%s'"
990  " in pythonHandles map",
991  *handle,
992  currentModule->m_name.c_str());
993  }
994  }
995 
996  PyGILState_Release(state);
997 }
998 
1003 static void logErrorMessage()
1004 {
1005  PyObject* type;
1006  PyObject* value;
1007  PyObject* traceback;
1008 
1009  numpyImportError = false;
1010 
1011  PyErr_Fetch(&type, &value, &traceback);
1012  PyErr_NormalizeException(&type, &value, &traceback);
1013 
1014  PyObject* str_exc_value = PyObject_Repr(value);
1015  PyObject* pyExcValueStr = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~");
1016  const char* pErrorMessage = value ?
1017  PyBytes_AsString(pyExcValueStr) :
1018  "no error description.";
1019  Logger::getLogger()->warn("logErrorMessage: Error '%s', plugin '%s'",
1020  pErrorMessage,
1021  gPluginName.c_str());
1022 
1023  // Check for numpy/pandas import errors
1024  const char *err1 = "implement_array_function method already has a docstring";
1025  const char *err2 = "cannot import name 'check_array_indexer' from 'pandas.core.indexers'";
1026 
1027  numpyImportError = strstr(pErrorMessage, err1) || strstr(pErrorMessage, err2);
1028 
1029  std::string fcn = "";
1030  fcn += "def get_pretty_traceback(exc_type, exc_value, exc_tb):\n";
1031  fcn += " import sys, traceback\n";
1032  fcn += " lines = []\n";
1033  fcn += " lines = traceback.format_exception(exc_type, exc_value, exc_tb)\n";
1034  fcn += " output = '\\n'.join(lines)\n";
1035  fcn += " return output\n";
1036 
1037  PyRun_SimpleString(fcn.c_str());
1038  PyObject* mod = PyImport_ImportModule("__main__");
1039  if (mod != NULL) {
1040  PyObject* method = PyObject_GetAttrString(mod, "get_pretty_traceback");
1041  if (method != NULL) {
1042  PyObject* outStr = PyObject_CallObject(method, Py_BuildValue("OOO", type, value, traceback));
1043  if (outStr != NULL) {
1044  PyObject* tmp = PyUnicode_AsASCIIString(outStr);
1045  if (tmp != NULL) {
1046  std::string pretty = PyBytes_AsString(tmp);
1047  Logger::getLogger()->warn("%s", pretty.c_str());
1048  Logger::getLogger()->printLongString(pretty.c_str());
1049  }
1050  Py_CLEAR(tmp);
1051  }
1052  Py_CLEAR(outStr);
1053  }
1054  Py_CLEAR(method);
1055  }
1056 
1057  // Reset error
1058  PyErr_Clear();
1059 
1060  // Remove references
1061  Py_CLEAR(type);
1062  Py_CLEAR(value);
1063  Py_CLEAR(traceback);
1064  Py_CLEAR(str_exc_value);
1065  Py_CLEAR(pyExcValueStr);
1066  Py_CLEAR(mod);
1067 }
1068 
1074 static void plugin_shutdown_fn(PLUGIN_HANDLE handle)
1075 {
1076  if (!handle)
1077  {
1078  Logger::getLogger()->fatal("plugin_handle: plugin_shutdown_fn: "
1079  "handle is NULL");
1080  return;
1081  }
1082 
1083  if (!pythonHandles)
1084  {
1085  Logger::getLogger()->error("pythonHandles map is NULL "
1086  "in plugin_shutdown_fn, plugin handle '%p'",
1087  handle);
1088  return;
1089  }
1090 
1091  // Look for Python module for handle key
1092  auto it = pythonHandles->find(handle);
1093  if (it == pythonHandles->end() ||
1094  !it->second ||
1095  !it->second->m_module)
1096  {
1097  Logger::getLogger()->fatal("plugin_handle: plugin_shutdown_fn: "
1098  "pModule is NULL, plugin handle '%p'",
1099  handle);
1100  return;
1101  }
1102 
1103  if (! Py_IsInitialized()) {
1104 
1105  Logger::getLogger()->debug("%s - Python environment not initialized, exiting from the function ", __FUNCTION__);
1106  return;
1107  }
1108 
1109  PyObject* pFunc;
1110  PyGILState_STATE state = PyGILState_Ensure();
1111 
1112  // Fetch required method in loaded object
1113  pFunc = PyObject_GetAttrString(it->second->m_module, "plugin_shutdown");
1114  if (!pFunc)
1115  {
1116  Logger::getLogger()->fatal("Cannot find method 'plugin_shutdown' "
1117  "in loaded python module '%s'",
1118  it->second->m_name.c_str());
1119  }
1120 
1121  if (!pFunc || !PyCallable_Check(pFunc))
1122  {
1123  // Failure
1124  if (PyErr_Occurred())
1125  {
1126  logErrorMessage();
1127  }
1128 
1129  Logger::getLogger()->fatal("Cannot call method 'plugin_shutdown' "
1130  "in loaded python module '%s'",
1131  it->second->m_name.c_str());
1132  Py_CLEAR(pFunc);
1133 
1134  PyGILState_Release(state);
1135  return;
1136  }
1137 
1138  // Call Python method passing an object
1139  PyObject* pReturn = PyObject_CallFunction(pFunc,
1140  "O",
1141  handle);
1142 
1143  Py_CLEAR(pFunc);
1144 
1145 
1146  if (false) // no seperate python interpreter is used anymore for python plugins
1147  {
1148  // Switch to Interpreter thread
1149  PyThreadState* swapState = PyThreadState_Swap(it->second->m_tState);
1150 
1151  // Remove Python module
1152  Py_CLEAR(it->second->m_module);
1153  it->second->m_module = NULL;
1154 
1155  // Stop Interpreter thread
1156  Py_EndInterpreter(it->second->m_tState);
1157 
1158  Logger::getLogger()->debug("plugin_shutdown_fn: Py_EndInterpreter of '%p' "
1159  "for plugin '%s'",
1160  it->second->m_tState,
1161  it->second->m_name.c_str());
1162  // Return to main thread
1163  PyThreadState_Swap(swapState);
1164 
1165  // Set pointer to null
1166  it->second->m_tState = NULL;
1167  }
1168  else
1169  {
1170  // Remove Python module
1171  Py_CLEAR(it->second->m_module);
1172  it->second->m_module = NULL;
1173  }
1174 
1175  PythonModule* module = it->second;
1176  string pName = it->second->m_name;
1177 
1178  // Remove item
1179  pythonHandles->erase(it);
1180 
1181  // Look for Python module, pName is the key
1182  auto m = pythonModules->find(pName);
1183  if (m != pythonModules->end())
1184  {
1185  // Remove this element
1186  pythonModules->erase(m);
1187  }
1188 
1189  // Release module object
1190  delete module;
1191  module = NULL;
1192 
1193  // Release GIL
1194  PyGILState_Release(state);
1195 
1196  Logger::getLogger()->debug("plugin_shutdown_fn succesfully "
1197  "called for plugin '%s'",
1198  pName.c_str());
1199 }
1200 
1201 };
1202 #endif
1203 
static PluginManager * getInstance()
PluginManager Singleton implementation.
Definition: plugin_manager.cpp:43
unsigned int options
The set of option flags that apply to this plugin.
Definition: plugin_api.h:29
Definition: config_category.h:56
The manager for plugins.
Definition: plugin_manager.h:40
const char * config
The default JSON configuration category for this plugin.
Definition: plugin_api.h:38
const char * interface
The interface version of this plugin.
Definition: plugin_api.h:36
Definition: asset_tracking.h:108
std::string getValue(const std::string &name) const
Return the value of the configuration category item.
Definition: config_category.cpp:449
std::string itemsToJSON(const bool full=false) const
Return JSON string of category items only.
Definition: config_category.cpp:1159
const char * version
The release version of the plugin.
Definition: plugin_api.h:27
const char * name
The name of the plugin.
Definition: plugin_api.h:25
The plugin infiornation structure, used to return information from a plugin during the laod and confi...
Definition: plugin_api.h:23
PLUGIN_INFORMATION * getInfo(const PLUGIN_HANDLE)
Return the information for a named plugin.
Definition: plugin_manager.cpp:531
const char * type
The plugin type, this is one of storage, south, filter, north, notificationRule or notificationDelive...
Definition: plugin_api.h:34
This class represents the loaded Python module with interpreter initialisation flag.
Definition: python_plugin_common_interface.h:27
PLUGIN_HANDLE findPluginByName(const std::string &name)
Find a loaded plugin by name.
Definition: plugin_manager.cpp:507