Coverage for gws-app/gws/server/control.py: 20%

84 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Functions to start, reconfigure and reload the servers.""" 

2 

3import gws 

4import gws.config 

5import gws.lib.datetimex 

6import gws.lib.osx 

7import gws.lib.watcher 

8 

9# see bin/gws 

10_SERVER_START_SCRIPT = f'{gws.c.VAR_DIR}/server.sh' 

11 

12_PID_PATHS = { 

13 'web': f'{gws.c.PIDS_DIR}/web.uwsgi.pid', 

14 'spool': f'{gws.c.PIDS_DIR}/spool.uwsgi.pid', 

15 'nginx': f'{gws.c.PIDS_DIR}/nginx.pid', 

16} 

17 

18 

19def start(manifest_path='', config_path=''): 

20 """Configure the application and write the server configuration files and the start script. 

21 

22 Called once on the container start. The start script itself is executed by ``bin/gws``. 

23 Exits the process with code 1 if the web server is already running. 

24 

25 Args: 

26 manifest_path: Path to the application manifest. 

27 config_path: Path to the configuration file. 

28 

29 Raises: 

30 ``gws.ConfigurationError``: If the configuration fails. 

31 """ 

32 if app_is_running('web'): 

33 gws.log.error(f'server already running') 

34 gws.u.exit(1) 

35 root = configure_and_store(manifest_path, config_path, is_starting=True) 

36 root.app.serverMgr.create_server_configs(gws.c.SERVER_DIR, _SERVER_START_SCRIPT, _PID_PATHS) 

37 

38 

39def reconfigure(manifest_path='', config_path=''): 

40 """Configure the application, rewrite the server configuration files and reload all servers. 

41 

42 Exits the process with code 1 if the web server is not running. 

43 

44 Args: 

45 manifest_path: Path to the application manifest. 

46 config_path: Path to the configuration file. 

47 

48 Raises: 

49 ``gws.ConfigurationError``: If the configuration fails. 

50 """ 

51 if not app_is_running('web'): 

52 gws.log.error(f'server not running') 

53 gws.u.exit(1) 

54 root = configure_and_store(manifest_path, config_path, is_starting=False) 

55 root.app.serverMgr.create_server_configs(gws.c.SERVER_DIR, _SERVER_START_SCRIPT, _PID_PATHS) 

56 reload_all() 

57 

58 

59def configure_and_store(manifest_path='', config_path='', is_starting=False): 

60 """Configure the application and store the configuration, so that the servers can load it. 

61 

62 Args: 

63 manifest_path: Path to the application manifest. 

64 config_path: Path to the configuration file. 

65 is_starting: True on the server start, runs the ``server.autoRun`` command before initialization. 

66 

67 Returns: 

68 The configured root object. 

69 

70 Raises: 

71 ``gws.ConfigurationError``: If the configuration fails. 

72 """ 

73 root = configure(manifest_path, config_path, is_starting) 

74 gws.config.save(root) 

75 return root 

76 

77 

78def configure(manifest_path='', config_path='', is_starting=False): 

79 """Configure the application and log the configuration report. 

80 

81 If the configuration fails and the manifest enables ``withFallbackConfig``, a minimal fallback configuration is used. 

82 

83 Args: 

84 manifest_path: Path to the application manifest. 

85 config_path: Path to the configuration file. 

86 is_starting: True on the server start, runs the ``server.autoRun`` command before initialization. 

87 

88 Returns: 

89 The configured root object. 

90 

91 Raises: 

92 ``gws.ConfigurationError``: If the configuration fails. 

93 """ 

94 def _pre_init(ld: gws.config.loader.Object): 

95 autorun = gws.u.get(ld.config, 'server.autoRun') 

96 if autorun: 

97 gws.log.info(f'AUTORUN: {autorun!r}') 

98 gws.lib.osx.run(autorun, echo=True) 

99 

100 hooks = [] 

101 if is_starting: 

102 hooks.append(['preInitialize', _pre_init]) 

103 

104 cr = gws.config.configure( 

105 manifest_path=manifest_path, 

106 config_path=config_path, 

107 fallback_config=_FALLBACK_CONFIG, 

108 hooks=hooks, 

109 ) 

110 gws.config.log_report(cr) 

111 if not cr.root: 

112 raise gws.ConfigurationError('configuration failed') 

113 return gws.u.require(cr.root) 

114 

115 

116def config_test( 

117 manifest_path='', 

118 config_path='', 

119 dirs_to_watch='', 

120 with_parse_only=False, 

121 with_watch=False 

122): 

123 """Configure or parse the configuration and log the report. 

124 

125 In the watch mode, the test is repeated whenever a file in the watched directories changes, 

126 and the function never returns. 

127 

128 Args: 

129 manifest_path: Path to the application manifest. 

130 config_path: Path to the configuration file. 

131 dirs_to_watch: Directories to watch, ``/data`` by default. 

132 with_parse_only: Only parse the configuration, do not configure the objects. 

133 with_watch: Keep watching the directories and repeat the test on changes. 

134 """ 

135 

136 def _check(*args): 

137 gws.log.info('=' * 80) 

138 gws.log.info(f'TESTING CONFIGURATION...') 

139 gws.log.info('=' * 80) 

140 if with_parse_only: 

141 cr = gws.config.parse(manifest_path, config_path) 

142 else: 

143 cr = gws.config.configure(manifest_path, config_path) 

144 gws.config.log_report(cr) 

145 

146 _check() 

147 

148 if not with_watch: 

149 return 

150 

151 w = gws.lib.watcher.new(_check) 

152 dirs = gws.u.to_list(dirs_to_watch or '/data') 

153 for d in dirs: 

154 w.add_directory(d, recursive=True) 

155 w.start() 

156 

157 while True: 

158 gws.u.sleep(3) 

159 

160 

161 

162## 

163 

164def reload_all(): 

165 """Empty the transient directory and reload the spool and web servers and NGINX. 

166 

167 Returns: 

168 Always True. 

169 """ 

170 gws.lib.osx.run(['rm', '-fr', gws.c.TRANSIENT_DIR]) 

171 gws.u.ensure_system_dirs() 

172 

173 reload_app('spool') 

174 reload_app('web') 

175 

176 reload_nginx() 

177 return True 

178 

179 

180def reload_app(srv): 

181 """Reload a uWSGI backend, if it is running. 

182 

183 Args: 

184 srv: Backend name, ``web`` or ``spool``. 

185 """ 

186 if not app_is_running(srv): 

187 gws.log.debug(f'reload: {srv=} not running') 

188 return 

189 gws.log.info(f'reloading {srv}...') 

190 gws.lib.osx.run(['uwsgi', '--reload', _PID_PATHS[srv]]) 

191 

192 

193def reload_nginx(): 

194 """Reload the NGINX configuration.""" 

195 gws.log.info(f'reloading nginx...') 

196 gws.lib.osx.run(['nginx', '-c', gws.c.SERVER_DIR + '/nginx.conf', '-s', 'reload']) 

197 

198 

199def app_is_running(srv): 

200 """Check whether a server is running, by its pid file. 

201 

202 Args: 

203 srv: Server name, ``web``, ``spool`` or ``nginx``. 

204 

205 Returns: 

206 True if the pid from the pid file belongs to a running process. 

207 """ 

208 try: 

209 with open(_PID_PATHS[srv]) as fp: 

210 pid = int(fp.read()) 

211 except (FileNotFoundError, ValueError): 

212 pid = 0 

213 if pid == 0: 

214 return False 

215 gws.log.debug(f'found {pid=} for {srv=}') 

216 return pid in gws.lib.osx.running_pids() 

217 

218 

219## 

220 

221 

222_FALLBACK_CONFIG = gws.Config( 

223 server=gws.Config( 

224 timeZone="Europe/Berlin", 

225 monitor=gws.Config(disabled=True), 

226 log=gws.Config(level='INFO'), 

227 qgis=gws.Config(host='qgis', port=80), 

228 spool=gws.Config(disabled=True), 

229 web=gws.Config(disabled=False, workers=1, timeout=60, maxRequestLength=10), 

230 ) 

231)