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 import client.test.auth_info; 61 62 immutable helpStr = 63 "JSON-RPC client for testing purposes of main rpc-server.\n" 64 " pgator-client [arguments]\n\n" 65 " arguments = --host=<string> - rpc-server url\n" 66 " --conn=<string> - postgres server conn string\n" 67 " --tableName=<string> - json_rpc table\n" 68 " --serverpid=<uint> - rpc server pid\n"; 69 70 uint getPid() 71 { 72 uint pid; 73 File("/var/run/pgator/pgator.pid", "r").readf("%d", &pid); 74 75 return pid; 76 } 77 78 // Getting pid via pgrep 79 uint getPidConsole() 80 { 81 return parse!uint(executeShell("pgrep pgator").output); 82 } 83 84 int main(string[] args) 85 { 86 string host = "http://127.0.0.1:8080"; 87 string connString; 88 string tableName = "json_rpc"; 89 uint pid; 90 bool help = false; 91 92 getopt(args, 93 "host", &host, 94 "help|h", &help, 95 "conn", &connString, 96 "tableName", &tableName, 97 "serverpid", &pid 98 ); 99 100 if(help || connString == "") 101 { 102 writeln(helpStr); 103 return 1; 104 } 105 106 if(pid == 0) 107 { 108 writeln("Trying to read pid file at '/var/run/pgator/pgator.pid'"); 109 try pid = getPid(); 110 catch(Exception e) 111 { 112 writeln("Trying to read pid with pgrep"); 113 try pid = getPidConsole(); 114 catch(Exception e) 115 { 116 writeln("Cannot find pgator process!"); 117 return 1; 118 } 119 } 120 } 121 122 auto client = new RpcClient!( 123 SimpleTestCase, 124 NullTestCase, 125 TimestampCase, 126 NumericTestCase, 127 UnicodeTestCase, 128 MulticommandCase, 129 NoticeTestCase, 130 SingleQueryTestCase, 131 OneRowTestCase, 132 ArrayTestCase, 133 LongQueryTestCase, 134 AuthInfoTestCase, 135 )(host, connString, tableName, pid); 136 scope(exit) client.finalize; 137 138 client.runTests(); 139 140 return 0; 141 } 142 } 143 else 144 { 145 import std.stdio; 146 import std.typecons; 147 import std.concurrency; 148 import std.process; 149 import std.string; 150 import core.time; 151 import server.server; 152 import server.options; 153 import server.config; 154 155 import daemon; 156 import terminal; 157 import dlogg.strict; 158 import util; 159 160 immutable struct LoadedConfig 161 { 162 AppConfig config; 163 Options options; 164 } 165 166 LoadedConfig loadConfig(immutable Options options) 167 { 168 if(options.configName != "") 169 { 170 return LoadedConfig(immutable AppConfig(options.configName), options); 171 } 172 else 173 { 174 auto res = tryConfigPaths(options.configPaths); 175 return LoadedConfig(res.config, options.updateConfigPath(res.path)); 176 } 177 } 178 179 /** 180 * Converts group and user names to corresponding gid and uid. If the $(B groupName) or 181 * $(B userName) are already a ints, simply converts them and returns. 182 * 183 * Retrieving of user id is performed by 'id -u %s' and group id by 'getent group %s | cut -d: -f3'. 184 */ 185 Tuple!(int, int) resolveRootLowing(shared ILogger logger, string groupName, string userName) 186 { 187 int tryConvert(string what)(string s, string command) 188 { 189 enum warningMsg = "Failed to retrieve " ~ what ~ " id for root lowing: "; 190 int handleFail(lazy string msg) 191 { 192 logger.logWarning(text(warningMsg, msg)); 193 return -1; 194 } 195 196 try return s.to!int; 197 catch(ConvException e) 198 { 199 try 200 { 201 auto res = executeShell(command.format(s)); 202 if(res.status != 0) return handleFail(res.output); 203 204 try return res.output.parse!int; 205 catch(ConvException e) return handleFail(e.msg); 206 } 207 catch(ProcessException e) return handleFail(e.msg); 208 catch(StdioException e) return handleFail(e.msg); 209 } 210 } 211 212 return tuple(tryConvert!"group"(groupName, "getent group %s | cut -d: -f3") 213 , tryConvert!"user"(userName, "id -u %s")); 214 } 215 216 int main(string[] args) 217 { 218 auto options = new immutable Options(args); 219 220 if (options.help) 221 { 222 writeln(options.helpMsg); 223 return 0; 224 } 225 if (options.showVersion) 226 { 227 writeln(options.versionMsg); 228 return 0; 229 } 230 231 if (options.genConfigPath != "") 232 { 233 genConfig(options.genConfigPath); 234 return 0; 235 } 236 237 try 238 { 239 auto loadedConfig = loadConfig(options); 240 auto logger = new shared StrictLogger(loadedConfig.config.logname, StrictLogger.Mode.Append); 241 auto app = new shared Application(logger, loadedConfig.options, loadedConfig.config); 242 243 enum mainFunc = (string[] args) 244 { 245 int res; 246 do 247 { 248 res = app.run; 249 } while(receiveTimeout(dur!"msecs"(1000), 250 // bug, should be fixed in 2.067 251 // (shared(Application) newApp) {app = newApp;} 252 (Variant v) 253 { 254 auto newAppPtr = v.peek!(shared(Application)); assert(newAppPtr); 255 app = *newAppPtr; 256 })); 257 258 logger.logDebug("Exiting main"); 259 return res; 260 }; 261 262 enum termFunc = () 263 { 264 auto newApp = app.restart; 265 send(thisTid, newApp); 266 }; 267 268 int groupid, userid; 269 tie!(groupid, userid) = resolveRootLowing(logger, loadedConfig.config.groupid, loadedConfig.config.userid); 270 271 if(options.daemon) 272 return runDaemon(logger, mainFunc, args, termFunc 273 , (){app.finalize;}, () {app.logger.reload;} 274 , options.pidFile, options.lockFile 275 , groupid, userid); 276 else 277 return runTerminal(logger, mainFunc, args, termFunc 278 , (){app.finalize;}, () {app.logger.reload;} 279 , groupid, userid); 280 } 281 catch(InvalidConfig e) 282 { 283 writeln("Configuration file at '", e.confPath, "' is invalid! ", e.msg); 284 return 1; 285 } 286 catch(NoConfigLoaded e) 287 { 288 writeln(e.msg); 289 return 1; 290 } 291 catch(Exception e) 292 { 293 writeln("Failed to load configuration file at '", options.configName, "'! Details: ", e.msg); 294 return 1; 295 } 296 } 297 }