Browse Source

[broken] Starting multisite loader rewriting refs #235

At this point the loader in lodel/plugins/multisite/run.py is theorically
complete & correct. But some features are missing in order to be able
to test & debug it ( refs #275 ).
Yann Weber 8 years ago
parent
commit
9df0443a8b

+ 19
- 0
lodel/plugins/multisite/__init__.py View File

@@ -1,3 +1,22 @@
1
+##@brief 
2
+#
3
+#@par Notes on FS organisation
4
+#
5
+#The plan is as follow :
6
+#
7
+#The autotools deploiment chain or the debian package will create a var 
8
+#folder dedicated for lodel2 ( for example /var/lodel2 ). This folder will
9
+#contains folder named as the lodesites instances (lodelsites is a lodel site
10
+#that handles lodel site as content). We will call this folder multisite
11
+#folder or lodelsites folder.
12
+#The multisite folder contains 2 settings folders :
13
+#- lodelsites.conf.d : the lodelsites configuration
14
+#- server.conf.d : the multisite process configuration
15
+#- datas : a folder containing datas for each site handled by the lodelsites
16
+#- .contexts : a folder containing context stuff for each site handlers by
17
+#the lodelsites ( the lodel package symlink + dyncode.py)
18
+#
19
+
1 20
 from lodel.context import LodelContext, ContextError
2 21
 try:
3 22
     LodelContext.expose_modules(globals(), {

+ 3
- 1
lodel/plugins/multisite/confspecs.py View File

@@ -5,7 +5,9 @@ LodelContext.expose_modules(globals(), {
5 5
 #Define a minimal confspec used by multisite loader
6 6
 LODEL2_CONFSPECS = {
7 7
     'lodel2': {
8
-        'debug': (True, SettingValidator('bool'))
8
+        'debug': (True, SettingValidator('bool')),
9
+        'sites_handler_name': (None,
10
+            SettingValidator('string', none_is_valid = False)),
9 11
     },
10 12
     'lodel2.server': {
11 13
         'listen_address': ('127.0.0.1', SettingValidator('dummy')),

+ 13
- 0
lodel/plugins/multisite/loader.py View File

@@ -4,11 +4,24 @@ import sys
4 4
 import shlex
5 5
 import warnings
6 6
 
7
+##@brief Preloader for a multisite process
8
+#
9
+#This loader is a kind of fake loader. In fact it only read configurations
10
+#for the multisite instance and then run a UWSGI process that will run
11
+#the run.py file.
12
+#
13
+#If you want to see the "real" multisite loading process see
14
+#@ref lodel/plugins/multisite/run.py file
15
+#
16
+#@par Implementation details 
7 17
 #Here we have to bootstrap a minimal __loader__ context in order
8 18
 #to be able to load the settings
9 19
 #
10 20
 #This file (once bootstraped) start a new process for uWSGI. uWSGI then
11 21
 #run lodel.plugins.multisite.run.application function
22
+#@note the uwsgi process in started using the execl function when UWSGI
23
+#will exit this process will stop too
24
+#
12 25
 try:
13 26
     from lodel.context import LodelContext
14 27
 except ImportError:

+ 168
- 56
lodel/plugins/multisite/run.py View File

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

Loading…
Cancel
Save