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 }