No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

run.py 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import os.path
  4. import warnings
  5. ##@brief File designed to be executed by an UWSGI process in order to
  6. #run a multisite instance process
  7. #
  8. #@warning In this module we have to be VERY carrefull about module exposure !
  9. #in fact here we will go through every context at least once and the globals
  10. #will be shared all among the module. So ANY exposed module will be accessible
  11. #for a short time outside its context !! A good way be protected about that
  12. #is to del(globals()[exposed_module_name]) after use. But it's really not
  13. #sure that this way of doing is a real safe protection !
  14. #
  15. #@par Expected context when called
  16. #- cwd has to be the lodelsites directory (the directory containing the
  17. #conf.d folder)
  18. #
  19. #This file is divided in two blocks :
  20. #
  21. #@par Run at load
  22. #The main function will be called at import.
  23. #This piece of code handles :
  24. #- loading the lodelsites site (the site that handles sites ;) )
  25. #- fetch the list of handled lodel sites
  26. #- loading the whole list of lodel sites
  27. #
  28. #@par WSGI processing
  29. #The other functions are here to handles WSGI request. The entry function
  30. #is application(), following the PEP 3333 specifications
  31. #
  32. # Constants declarations
  33. #
  34. ##@brief basename of multisite process conf folder
  35. #@todo find a better place to declare it
  36. SERVER_CONFD = 'server.conf.d' #Should be accessible elsewhere
  37. ##@brief basename of lodelsites site conf folder
  38. #@todo find a better place to declare it
  39. LODELSITES_CONFD = 'lodelsites.conf.d' #Should be accessible elsewhere
  40. ##@brief A cache allowing a fast application exposure
  41. #
  42. #This dict contains reference on interface module of each handled site in
  43. #order to quickly call the application (PEP 3333) function of concerned site
  44. FAST_APP_EXPOSAL_CACHE = dict()
  45. try:
  46. from lodel.context import LodelContext
  47. except ImportError:
  48. LODEL_BASE_DIR = os.path.dirname(
  49. os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  50. from lodel.context import LodelContext, ContextError
  51. import os.path
  52. import lodel.buildconf #safe even outside contexts
  53. ##@brief Function that will run at import time
  54. #
  55. #Handles lodelsites site loading, handled site list fecth & load
  56. #@note called at end of file
  57. #
  58. #@todo evaluate if it is safe to assume that lodelsites_datapath = os.getcwd()
  59. #@todo get rid of hardcoded stuff (like shortname fieldname)
  60. #@todo use the dyncode getter when it will be available (replaced by
  61. #the string SUPERDYNCODE_ACCESSOR.Lodelsite for the moment)
  62. def main():
  63. LodelContext.init(LodelContext.MULTISITE)
  64. #Set current context to reserved loader context
  65. LodelContext.set(None)
  66. LodelContext.expose_modules(globals(), {
  67. 'lodel.logger': 'logger',
  68. 'lodel.exceptions': ['LodelFatalError'],
  69. })
  70. CONFDIR = os.path.join(os.getcwd(), SERVER_CONFD)
  71. if not os.path.isdir(CONFDIR):
  72. logger.critical('Multisite process bootstraping fails : unable to \
  73. find the %s folder' % SERVER_CONFD)
  74. #Settings bootstraping for mutlisite process
  75. LodelContext.expose_modules(globals(), {
  76. 'lodel.settings.settings': [('Settings', 'settings')],
  77. 'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
  78. settings(CONFDIR, mutisite_confspecs.LODEL2_CONFSPECS)
  79. #Loading settings
  80. del(globals()['settings']) #useless but may be safer
  81. #Exposing "real" settings object in loader context
  82. LodelContext.expose_modules(globals(), {
  83. 'lodel.settings': ['Settings']})
  84. #Fetching lodelsites informations
  85. lodelsites_name = Settings.sites_handler_name
  86. #Following path construction is kind of dirty ! We should be able
  87. #to assume that the lodelsites_datapath == os.getcwd()....
  88. lodelsites_datapath = os.path.join(LODEL2VARDIR, lodelsites_name)
  89. #loading lodelsites site
  90. load_site(lodelsites_datapath, LODELSITES_CONFD)
  91. #Fetching handled sites list
  92. #WARNING ! Here we assert that context name == basename(lodelsites_datapath)
  93. LodelContext.set(lodelsites_name)
  94. #in lodelsites context
  95. Lodelsite_leo = SUPERDYNCODE_ACCESSOR.Lodelsite #hardcoded leo name
  96. LodelContext.expose_modules(globals(), {
  97. 'lodel.leapi.query': ['LeGetQuery'],
  98. })
  99. #the line bellow you will find another harcoded thing : the shortname
  100. #fieldname for a lodelsite
  101. handled_sites = LeGetQuery(lodelsite_leo, query_filters = [],
  102. field_list = ['shortname'])
  103. #Now that we have the handled sitenames list we can go back to
  104. #loader context and clean it
  105. LodelContext.set(None)
  106. for mname in ['LeGetQuery', 'Settings']:
  107. del(globals()[mname])
  108. #Loading handled sites
  109. for handled_sitename in [s['shortname'] for s in handled_sites]:
  110. datapath = os.path.join(lodelsites_datapath, handled_sitename)
  111. site_load(datapath) #using default conf.d configuration dirname
  112. ##@brief Load a site
  113. #
  114. #Apply a common (as MONOSITE) loading process to a site :
  115. #1. Conf preload
  116. #2. Plugins preload
  117. #3. Conf loading
  118. #4. starting plugins & hooks
  119. #@warning At this point we need a uniq identifier for the site (using it
  120. #as key for contexts & FAST_APP_EXPOSAL_CACHE). To achieve this we use
  121. #the data_path basename. It should works for handled sites and for the
  122. #lodelsites instance
  123. #@param data_path str : path to the datas directory (containing the confdir)
  124. #@param confdir_basename str : the basename of the site confdir
  125. #
  126. #@todo For now the interface plugin name for sites is hardcoded (set to
  127. #webui). It HAS TO be loaded from settings. But it is a bit complicated,
  128. #we have to get the plugin's module name abstracted from context :
  129. #lodel.something but if we ask directly to Plugin class the module name
  130. #it will return something like : lodelsites.sitename.something...
  131. #
  132. #@todo there is a quick & dirty workarround with comments saying that it
  133. #avoid context escape via hooks. We have to understand why and how and then
  134. #replace the workarround by a real solution !
  135. def site_load(data_path, confdir_basename = 'conf.d'):
  136. #args check
  137. if confdir_basename != os.path.basename(confdir_basename):
  138. LodelFatalError('Bad argument given to site_load(). This really \
  139. sux !')
  140. #Determining uniq sitename from data_path
  141. data_path = data_path.rstrip('/') #else basename returns ''
  142. ctx_name = os.path.basename(data_path)
  143. #Immediately switching to the context
  144. LodelContext.set(ctx_name)
  145. os.chdir(data_path) #Now the confdir is ./$condir_basename
  146. #Loading settings for current site
  147. LodelContext.expose_modules(globals(), {
  148. 'lodel.settings.settings': [('Settings', 'settings_preloader')]})
  149. if settings_preloader.started():
  150. msg = 'Settings seems to be allready started for "%s". \
  151. This should not append !' % ctx_name
  152. #switch back to loader context in order to log & raise
  153. LodelContext.set(None)
  154. logger.critical(msg)
  155. raise LodelFatalError(msg)
  156. settings(os.path.join('./', confdir_basename))
  157. #
  158. #Loading hooks & plugins
  159. #
  160. LodelContext.expose_modules(globals(), {
  161. 'lodel.plugin': ['Plugin', 'LodelHook'],
  162. 'lodel.logger': 'logger',
  163. 'lodel.plugin.core_hooks': 'core_hooks',
  164. 'lodel.plugin.core_scripts': 'core_scripts'
  165. })
  166. Plugin.load_all() #Then all plugins & hooks are loaded
  167. #triggering dyncode datasource instanciations
  168. LodelHook.call_hook('lodel2_plugins_loaded', '__main__', None)
  169. #triggering boostrapped hook
  170. LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
  171. #Populating FAST_APP_EXPOSAL_CACHE
  172. #
  173. #WARNING !!!! Hardcoded interface name ! Here we have to find the
  174. #interface plugin name in order to populate the cache properly
  175. FAST_APP_EXPOSAL_CACHE[ctx_name] = LodelContext.module(
  176. 'lodel.plugins.webui.run')
  177. #a dirty & quick attempt to fix context unwanted exite via
  178. #hooks
  179. for name in ( 'LodelHook', 'core_hooks', 'core_scripts',
  180. 'Settings', 'settings', 'logger', 'Plugin'):
  181. del(globals()[name])
  182. #site fully loaded, switching back to loader context
  183. LodelContext.set(None)
  184. #lodel2 multisite instances are loaded and ready to run
  185. ##@brief Utility function to return quickly an error
  186. def http_error(env, start_response, status = '500 internal server error', \
  187. extra = None):
  188. headers = [('Content-type', 'text/plain; charset=utf-8')]
  189. start_response(status, headers)
  190. msg = status
  191. if extra is not None:
  192. msg = extra
  193. return [msg.encode('utf-8')]
  194. ##@brief utility function to extract site id from an url
  195. #@param url str :
  196. def site_id_from_url(url):
  197. res = ''
  198. for c in url[1:]:
  199. if c == '/':
  200. break
  201. res += c
  202. if len(res) == 0:
  203. return None
  204. return res
  205. ##@brief This method is run in a child process by the handler
  206. def application(env, start_response):
  207. #Attempt to load a context
  208. site_id = site_id_from_url(env['PATH_INFO'])
  209. if site_id is None:
  210. #It can be nice to provide a list of instances here
  211. return http_error(env, start_response, '404 Not Found')
  212. try:
  213. LodelContext.set(site_id)
  214. #We are in the good context
  215. except ContextError as e:
  216. print(e)
  217. return http_error(env, start_response, '404 Not found',
  218. "No site named '%s'" % site_id)
  219. #Calling webui
  220. return FAST_APP_EXPOSAL_CACHE[site_id].application(env, start_response)
  221. #LodelContext.expose_modules(globals(), {
  222. # 'lodel.plugins.webui.run': ['application']})
  223. #return application(env, start_response)
  224. #calling the main function
  225. main()