RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
soa/service/testing/redis_temporary_server.h
00001 /* redis_temporary_server.h                                        -*- C++ -*-
00002    Jeremy Barnes, 19 October 2012
00003    Copyright (c) 2012 Datacratic Inc.  All rights reserved.
00004 
00005    A temporary server for testing of redis-based services.  Starts one up in a
00006    temporary directory and gives the uri to connect to.
00007 */
00008 
00009 #include "jml/utils/environment.h"
00010 #include "jml/utils/file_functions.h"
00011 #include "jml/arch/timers.h"
00012 #include "soa/service/redis.h"
00013 #include <sys/stat.h>
00014 #include <sys/types.h>
00015 #include <sys/wait.h>
00016 #include <sys/un.h>
00017 #include <sys/socket.h>
00018 #include <sys/prctl.h>
00019 #include <signal.h>
00020 
00021 namespace Redis {
00022 
00023 struct RedisTemporaryServer : boost::noncopyable {
00024 
00025     RedisTemporaryServer(std::string uniquePath = "")
00026     {
00027         using namespace std;
00028         if (uniquePath == "") {
00029             ML::Env_Option<std::string> tmpDir("TMP", "./tmp");
00030             uniquePath = ML::format("%s/redis-temporary-server-%d",
00031                                     tmpDir.get(), getpid());
00032             cerr << "starting redis temporary server under unique path "
00033                  << uniquePath << endl;
00034         }
00035 
00036         this->uniquePath = uniquePath;
00037         start();
00038     }
00039 
00040     ~RedisTemporaryServer()
00041     {
00042         shutdown();
00043     }
00044 
00045     void start()
00046     {
00047         using namespace std;
00048 
00049         // Check the unique path
00050         if (uniquePath == "" || uniquePath[0] == '/' || uniquePath == "."
00051             || uniquePath == "..")
00052             throw ML::Exception("unacceptable unique path");
00053 
00054         // 1.  Create the directory
00055 
00056 #if 0
00057         char cwd[1024];
00058 
00059         if (uniquePath[0] != '/')
00060             uniquePath = getcwd(cwd, 1024) + string("/") + uniquePath;
00061 
00062         cerr << "uniquePath = " << uniquePath << endl;
00063 #endif
00064 
00065         // First check that it doesn't exist
00066         struct stat stats;
00067         int res = stat(uniquePath.c_str(), &stats);
00068         if (res != -1 || (errno != EEXIST && errno != ENOENT))
00069             throw ML::Exception(errno, "unique path " + uniquePath
00070                                 + " already exists");
00071         
00072         res = system(ML::format("mkdir -p %s", uniquePath).c_str());
00073         if (res == -1)
00074             throw ML::Exception(errno, "couldn't mkdir");
00075         
00076         string unixPath = uniquePath + "/redis-socket";
00077 
00078         int UNIX_PATH_MAX=108;
00079 
00080         if (unixPath.size() >= UNIX_PATH_MAX)
00081             throw ML::Exception("unix socket path is too long");
00082 
00083         // Create unix socket
00084         res = mknod(unixPath.c_str(), 0777 | S_IFIFO, 0);
00085         if (res == -1)
00086             throw ML::Exception(errno, "couldn't create unix socket for Redis");
00087 
00088         //ML::set_permissions(unixPath, "777", "");
00089 
00090         // 2.  Start the server
00091         int pid = fork();
00092         if (pid == -1)
00093             throw ML::Exception(errno, "fork");
00094         if (pid == 0) {
00095             int res = prctl(PR_SET_PDEATHSIG, SIGHUP);
00096             if(res == -1) {
00097                 throw ML::Exception(errno, "prctl failed");
00098             }
00099 
00100             signal(SIGTERM, SIG_DFL);
00101             signal(SIGKILL, SIG_DFL);
00102 
00103             cerr << "running redis" << endl;
00104             res = execlp("redis-server",
00105                              "redis-server",
00106                              "--port", "0",
00107                              "--unixsocket", "./redis-socket",
00108                              "--dir", uniquePath.c_str(),
00109                              (char *)0);
00110             if (res == -1)
00111                 throw ML::Exception(errno, "redis failed to start");
00112 
00113             throw ML::Exception(errno, "redis failed to start");
00114         }
00115         else {
00116             serverPid = pid;
00117         }
00118 
00119         {
00120             // 3.  Connect to the server to make sure it works
00121             int sock = socket(AF_UNIX, SOCK_STREAM, 0);
00122             if (sock == -1)
00123                 throw ML::Exception(errno, "socket");
00124 
00125             sockaddr_un addr;
00126             addr.sun_family = AF_UNIX;
00127             strcpy(addr.sun_path, unixPath.c_str());
00128 
00129             // Wait for it to start up
00130             for (unsigned i = 0;  i < 1000;  ++i) {
00131                 res = connect(sock, (const sockaddr *)&addr, sizeof(addr));
00132                 if (res == 0) break;
00133                 if (res == -1 && errno != ECONNREFUSED)
00134                     throw ML::Exception(errno, "connect");
00135             
00136                 ML::sleep(0.01);
00137             }
00138 
00139             if (res != 0)
00140                 throw ML::Exception("redis didn't start up in 10 seconds");
00141 
00142             close(sock);
00143         }
00144 
00145         cerr << "address is " << unixPath << endl;
00146 
00147         this->address_ = Address::unix(unixPath);
00148     }
00149 
00150     void shutdown()
00151     {
00152         if (serverPid == -1)
00153             return;
00154 
00155         // Stop boost test framework from interpreting this as a problem...
00156         signal(SIGCHLD, SIG_DFL);
00157 
00158         int res = kill(serverPid, SIGTERM);
00159         if (res == -1)
00160             throw ML::Exception(errno, "kill redis");
00161 
00162         int status = 0;
00163         res = waitpid(serverPid, &status, 0);
00164         if (res == -1)
00165             throw ML::Exception(errno, "wait for redis shutdown");
00166 
00167         this->address_ = Address();
00168 
00169         serverPid = -1;
00170 
00171         if (uniquePath != "") {
00172             using namespace std;
00173             cerr << "removing " << uniquePath << endl;
00174             int rmstatus = system(("rm -rf " + uniquePath).c_str());
00175             if (rmstatus)
00176                 throw ML::Exception(errno, "removing redis path");
00177         }
00178     }
00179 
00180     Address address() const
00181     {
00182         return address_;
00183     }
00184 
00185     operator Address() const
00186     {
00187         return address();
00188     }
00189 
00190     Address address_;
00191     std::string uniquePath;
00192     int serverPid;
00193 };
00194 
00195 } // namespace Redis
00196 
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator