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

76 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-23 20:06 +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. 

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. 

87 

88 Returns: 

89 The configured root object. 

90 

91 Raises: 

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

93 """ 

94 cr = gws.config.configure( 

95 manifest_path=manifest_path, 

96 config_path=config_path, 

97 fallback_config=_FALLBACK_CONFIG, 

98 ) 

99 gws.config.log_report(cr) 

100 if not cr.root: 

101 raise gws.ConfigurationError('configuration failed') 

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

103 

104 

105def config_test( 

106 manifest_path='', 

107 config_path='', 

108 dirs_to_watch='', 

109 with_parse_only=False, 

110 with_watch=False 

111): 

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

113 

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

115 and the function never returns. 

116 

117 Args: 

118 manifest_path: Path to the application manifest. 

119 config_path: Path to the configuration file. 

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

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

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

123 """ 

124 

125 def _check(*args): 

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

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

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

129 if with_parse_only: 

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

131 else: 

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

133 gws.config.log_report(cr) 

134 

135 _check() 

136 

137 if not with_watch: 

138 return 

139 

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

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

142 for d in dirs: 

143 w.add_directory(d, recursive=True) 

144 w.start() 

145 

146 while True: 

147 gws.u.sleep(3) 

148 

149 

150 

151## 

152 

153def reload_all(): 

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

155 

156 Returns: 

157 Always True. 

158 """ 

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

160 gws.u.ensure_system_dirs() 

161 

162 reload_app('spool') 

163 reload_app('web') 

164 

165 reload_nginx() 

166 return True 

167 

168 

169def reload_app(srv): 

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

171 

172 Args: 

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

174 """ 

175 if not app_is_running(srv): 

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

177 return 

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

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

180 

181 

182def reload_nginx(): 

183 """Reload the NGINX configuration.""" 

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

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

186 

187 

188def app_is_running(srv): 

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

190 

191 Args: 

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

193 

194 Returns: 

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

196 """ 

197 try: 

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

199 pid = int(fp.read()) 

200 except (FileNotFoundError, ValueError): 

201 pid = 0 

202 if pid == 0: 

203 return False 

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

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

206 

207 

208## 

209 

210 

211_FALLBACK_CONFIG = gws.Config( 

212 server=gws.Config( 

213 withMonitor=False, 

214 withSpool=False, 

215 withWeb=True, 

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

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

218 web=gws.Config(workers=1, timeout=60, maxRequestLength=10), 

219 ) 

220)