1 /**
2 *   Application contains four build configurations: production, unittests, integration test 1,
3 *   integration test 2 and test client.
4 *
5 *   Unittests configuration produce dummy executable the only purpose is to run module unittests.
6 *
7 *   Production configuration is main and default configuration. There the configuration files and
8 *   argument parameters are parsed, daemon or terminal mode is selected and actual rpc server starts.
9 *
10 *   Integration test 1 performs simple tests on real PostgreSQL instance. The configuration expects
11 *   '--conn' parameter with valid connection string to test database. The main thing that is tested
12 *   is connection pool operational correctness.
13 *
14 *   Integration test 2 performs major tests on real PostgreSQL instance. The configuration expects
15 *   '--conn' parameter with valid connection string to test database. There are many tests for
16 *   binary converting from libpq format. This test is the most important one as it should expose
17 *   libp binary format changing while updating to new versions. 
18 *
19 *   Test client is most general integration tests set. First client expects that there is an instance
20 *   of production configuration is running already. The '--host' argument specifies URL the server is
21 *   binded to (usually 'http://localhost:8080). The '--serverpid' argument should hold the server 
22 *   process PID to enable automatic server reloading while changing json-rpc table. '--conn' and
23 *   '--tableName' specifies connection string to PostgreSQL and json-rpc table respectively that are 
24 *   used in server being tested.
25 *
26 *   Test client performs automatic rules addition to json-rpc table, testing request sending to
27 *   the production server and checking returned results. Finally test client cleans up used
28 *   testing rules in json-rpc table.
29 *
30 *   Copyright: © 2014 DSoftOut
31 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
32 *   Authors: NCrashed <ncrashed@gmail.com>
33 *            Zaramzan <shamyan.roman@gmail.com>
34 */
35 module app;
36 
37 version(unittest)
38 {
39     void main() {}
40 }
41 else version(RpcClient)
42 {
43     import std.getopt;
44     import std.process;
45     import std.conv;
46     import std.stdio;
47     import client.client;
48     import client.test.testcase;
49     import client.test.simple;
50     import client.test.nullcase;
51     import client.test.numeric;
52     import client.test.unicode;
53     import client.test.multicommand;
54     import client.test.longquery;
55     import client.test.notice;
56     import client.test.singlequery;
57     import client.test.onerow;
58     import client.test.array;
59     import client.test.timestamp;
60     
61     immutable helpStr =
62     "JSON-RPC client for testing purposes of main rpc-server.\n"
63     "   pgator-client [arguments]\n\n"
64     "   arguments = --host=<string> - rpc-server url\n"
65     "               --conn=<string> - postgres server conn string\n"
66     "               --tableName=<string> - json_rpc table\n"
67     "               --serverpid=<uint> - rpc server pid\n";
68     
69     uint getPid()
70     {
71         return parse!uint(executeShell("[ ! -f /var/run/pgator/pgator.pid ] || echo `cat /var/run/pgator/pgator.pid`").output);
72     }
73     
74     // Getting pid via pgrep
75     uint getPidConsole()
76     {
77         return parse!uint(executeShell("pgrep pgator").output);
78     }
79     
80     int main(string[] args)
81     {
82         string host = "http://127.0.0.1:8080";
83         string connString;
84         string tableName = "json_rpc";
85         uint pid;
86         bool help = false;
87         
88         getopt(args,
89             "host", &host,
90             "help|h", &help,
91             "conn", &connString,
92             "tableName", &tableName,
93             "serverpid", &pid
94         );
95         
96         if(help || connString == "")
97         {
98             writeln(helpStr);
99             return 1;
100         }
101         
102         if(pid == 0)
103         {
104             writeln("Trying to read pid file at '/var/run/pgator/pgator.pid'");
105             try pid = getPid();
106             catch(Exception e)
107             {
108                 writeln("Trying to read pid with pgrep");
109                 try pid = getPidConsole();
110                 catch(Exception e)
111                 {
112                     writeln("Cannot find pgator process!");
113                     return 1;
114                 }
115             }
116         }
117         
118         auto client = new RpcClient!(
119         	SimpleTestCase, 
120         	NullTestCase,
121         	TimestampCase,
122         	NumericTestCase,
123         	UnicodeTestCase,
124         	MulticommandCase,
125         	NoticeTestCase,
126         	SingleQueryTestCase,
127         	OneRowTestCase,
128         	ArrayTestCase,
129         	LongQueryTestCase,
130         	)(host, connString, tableName, pid);
131         scope(exit) client.finalize;
132         
133         client.runTests();
134         
135         return 0;
136     }
137 }
138 else
139 {
140     import std.stdio;
141     import std.typecons;
142     import std.concurrency;
143     import std.process;
144     import std.string;
145 	import core.time;
146 	import server.server;
147 	import server.options;
148 	import server.config;
149 	
150 	import daemon;
151 	import terminal;
152 	import dlogg.strict;
153 	import util;
154 	
155 	immutable struct LoadedConfig
156     {
157         AppConfig config;
158         Options options;
159     }
160     
161 	LoadedConfig loadConfig(immutable Options options)
162 	{
163         if(options.configName != "")
164         {
165             return LoadedConfig(immutable AppConfig(options.configName), options);
166         }
167         else
168         {
169             auto res = tryConfigPaths(options.configPaths); 
170             return LoadedConfig(res.config, options.updateConfigPath(res.path));
171         }
172 	}
173 	
174 	/**
175 	*  Converts group and user names to corresponding gid and uid. If the $(B groupName) or
176 	*  $(B userName) are already a ints, simply converts them and returns.
177 	*
178 	*  Retrieving of user id is performed by 'id -u %s' and group id by 'getent group %s | cut -d: -f3'.
179 	*/
180 	Tuple!(int, int) resolveRootLowing(shared ILogger logger, string groupName, string userName)
181 	{
182 	    int tryConvert(string what)(string s, string command)
183 	    {
184 	        enum warningMsg = "Failed to retrieve " ~ what ~ " id for root lowing: ";
185 	        int handleFail(lazy string msg)
186 	        {
187 	            logger.logWarning(text(warningMsg, msg));
188                 return -1;
189 	        }
190 	        
191 	        try return s.to!int;
192 	        catch(ConvException e)
193 	        {
194 	            try
195 	            {
196 	                auto res = executeShell(command.format(s));
197 	                if(res.status != 0) return handleFail(res.output);
198 	                
199 	                try return res.output.parse!int;
200 	                catch(ConvException e) return handleFail(e.msg);
201                 } 
202 	            catch(ProcessException e) return handleFail(e.msg);
203 	            catch(StdioException e) return handleFail(e.msg);
204 	        }
205 	    }
206 	    
207 	    return tuple(tryConvert!"group"(groupName, "getent group %s | cut -d: -f3")
208 	               , tryConvert!"user"(userName, "id -u %s"));
209 	}
210 	
211 	int main(string[] args)
212 	{	
213 		auto options = new immutable Options(args);
214 		
215 		if (options.help)
216 		{
217 			writeln(options.helpMsg);
218 			return 0;
219 		}
220 		if (options.showVersion)
221 		{
222 		    writeln(options.versionMsg);
223 		    return 0;
224 		}
225 		
226 		if (options.genConfigPath != "")
227 		{
228 			genConfig(options.genConfigPath);
229 			return 0;
230 		}
231 		
232 		try
233 		{
234 		    auto loadedConfig = loadConfig(options);
235             auto logger = new shared StrictLogger(loadedConfig.config.logname, StrictLogger.Mode.Append);
236             auto app = new shared Application(logger, loadedConfig.options, loadedConfig.config);
237             
238             enum mainFunc = (string[] args)
239             {
240                 int res;
241                 do
242                 {
243                     res = app.run;
244                 } while(receiveTimeout(dur!"msecs"(1000), 
245                         // bug, should be fixed in 2.067
246                         //  (shared(Application) newApp) {app = newApp;}
247                         (Variant v) 
248                         {
249                             auto newAppPtr = v.peek!(shared(Application)); assert(newAppPtr);
250                             app = *newAppPtr;
251                         }));
252                 
253                 logger.logDebug("Exiting main");
254                 return res;
255             };
256             
257             enum termFunc = ()
258             {
259                 auto newApp = app.restart;
260                 send(thisTid, newApp);
261             };
262 
263             int groupid, userid;
264             tie!(groupid, userid) = resolveRootLowing(logger, loadedConfig.config.groupid, loadedConfig.config.userid);
265             
266             if(options.daemon) 
267                 return runDaemon(logger, mainFunc, args, termFunc
268                     , (){app.finalize;}, () {app.logger.reload;}
269                     , options.pidFile, options.lockFile
270                     , groupid, userid);
271             else 
272                 return runTerminal(logger, mainFunc, args, termFunc
273                     , (){app.finalize;}, () {app.logger.reload;}
274                     , groupid, userid);
275 	    }
276 	    catch(InvalidConfig e)
277         {
278             writeln("Configuration file at '", e.confPath, "' is invalid! ", e.msg);
279             return 1;
280         }
281         catch(NoConfigLoaded e)
282         {
283             writeln(e.msg);
284             return 1;
285         }
286         catch(Exception e)
287         {
288             writeln("Failed to load configuration file at '", options.configName, "'! Details: ", e.msg);
289             return 1;
290         }
291 	}
292 }