{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/qserv/pulls/comments/238025796","pull_request_review_id":180479447,"id":238025796,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzODAyNTc5Ng==","diff_hunk":"@@ -0,0 +1,618 @@\n+// -*- LSST-C++ -*-\n+/*\n+ * LSST Data Management System\n+ * Copyright 2018 AURA/LSST.\n+ *\n+ * This product includes software developed by the\n+ * LSST Project (http://www.lsst.org/).\n+ *\n+ * This program is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation, either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the LSST License Statement and\n+ * the GNU General Public License along with this program.  If not,\n+ * see <http://www.lsstcorp.org/LegalNotices/>.\n+ */\n+\n+\n+// Class header\n+#include \"loader/ServerTcpBase.h\"\n+\n+// System headers\n+#include <iostream>\n+#include <unistd.h>\n+\n+// Third-party headers\n+\n+\n+// qserv headers\n+#include \"loader/CentralWorker.h\"\n+#include \"loader/LoaderMsg.h\"\n+\n+// LSST headers\n+#include \"lsst/log/Log.h\"\n+\n+namespace {\n+LOG_LOGGER _log = LOG_GET(\"lsst.qserv.loader.ServerTcpBase\");\n+\n+const int testNewNodeName = 73; // TODO Get rid of this, possibly make NodeName member of ServerTCPBase\n+unsigned int testNewNodeValuePairCount = 81;\n+const int testOldNodeName = 42; // TODO Get rid of this, possibly make NodeName member of ServerTCPBase\n+unsigned int testOldNodeKeyCount = 1231;\n+}\n+\n+namespace lsst {\n+namespace qserv {\n+namespace loader {\n+\n+\n+bool ServerTcpBase::writeData(AsioTcp::socket& socket, BufferUdp& data) {\n+    while (data.getBytesLeftToRead() > 0) {\n+        // Read cursor advances (manually in this case) as data is read from the buffer.\n+        auto res = boost::asio::write(socket,\n+                                      boost::asio::buffer(data.getReadCursor(), data.getBytesLeftToRead()));\n+        data.advanceReadCursor(res);\n+    }\n+    return true;\n+}\n+\n+\n+uint32_t ServerTcpBase::getOurName() {\n+    return (_centralWorker == nullptr) ? 0 : _centralWorker->getOurId();\n+}\n+\n+\n+bool ServerTcpBase::testConnect() {\n+    try\n+    {\n+        LOGS(_log, LOG_LVL_INFO, \"ServerTcpBase::testConnect 1\");\n+        boost::asio::io_context io_context;\n+\n+        AsioTcp::resolver resolver(io_context);\n+        AsioTcp::resolver::results_type endpoints = resolver.resolve(\"127.0.0.1\", std::to_string(_port));\n+\n+        AsioTcp::socket socket(io_context);\n+        boost::asio::connect(socket, endpoints);\n+\n+\n+        // Get name from server\n+        BufferUdp data(500);\n+        auto msgElem = data.readFromSocket(socket, \"ServerTcpBase::testConnect\");\n+        // First element should be UInt32Element with the other worker's name\n+        UInt32Element::Ptr nghName = std::dynamic_pointer_cast<UInt32Element>(msgElem);\n+        if (nghName == nullptr) {\n+            throw LoaderMsgErr(ERR_LOC, \"testConnect() first element wasn't correct type \" +\n+                               msgElem->getStringVal());\n+        }\n+\n+        LOGS(_log, LOG_LVL_INFO, \"server name=\" << nghName->element);\n+\n+        data.reset();\n+        UInt32Element kind(LoaderMsg::TEST);\n+        kind.appendToData(data);\n+        UInt32Element bytes(1234); // dummy value\n+        bytes.appendToData(data);\n+        writeData(socket, data);\n+\n+        // send back our name and left neighbor message.\n+        data.reset();\n+        UInt32Element imRightKind(LoaderMsg::IM_YOUR_R_NEIGHBOR);\n+        imRightKind.appendToData(data);\n+        UInt32Element ourName(testNewNodeName);\n+        ourName.appendToData(data);\n+        UInt64Element valuePairCount(testNewNodeValuePairCount);\n+        valuePairCount.appendToData(data);\n+        writeData(socket, data);\n+\n+        // Get back left neighbor information\n+        auto msgKind = std::dynamic_pointer_cast<UInt32Element>(\n+                       data.readFromSocket(socket, \"testConnect 2 kind\"));\n+        auto msgLNName =  std::dynamic_pointer_cast<UInt32Element>(\n+                          data.readFromSocket(socket, \"testConnect 2 LNName\"));\n+        auto msgLKeyCount = std::dynamic_pointer_cast<UInt64Element>(\n+                            data.readFromSocket(socket, \"testConnect 2 LKeyCount\"));\n+        if (msgKind == nullptr || msgLNName == nullptr || msgLKeyCount == nullptr) {\n+            LOGS(_log, LOG_LVL_ERROR, \"ServerTcpBase::testConnect 2 - nullptr\" <<\n+                  \" msgKind=\" << (msgKind ? \"ok\" : \"null\") <<\n+                  \" msgLNName=\" << (msgLNName ? \"ok\" : \"null\") <<\n+                  \" msgLKeyCount=\" << (msgLKeyCount ? \"ok\" : \"null\"));\n+            return false;\n+        }\n+\n+        if (msgKind->element != LoaderMsg::IM_YOUR_L_NEIGHBOR ||\n+            msgLNName->element != testOldNodeName ||\n+            msgLKeyCount->element != testOldNodeKeyCount) {\n+            LOGS(_log, LOG_LVL_ERROR, \"ServerTcpBase::testConnect 2 - incorrect data\" <<\n+                                      \" Kind=\" << msgKind->element <<\n+                                      \" LNName=\" << msgLNName->element <<\n+                                      \" LKeyCount=\" << msgLKeyCount->element);\n+            return false;\n+        }\n+        LOGS(_log, LOG_LVL_INFO, \"ServerTcpBase::testConnect 2 - ok data\" <<\n+                                 \" Kind=\" << msgKind->element <<\n+                                 \" LNName=\" << msgLNName->element <<\n+                                 \" LKeyCount=\" << msgLKeyCount->element);\n+\n+        data.reset();\n+        UInt32Element verified(LoaderMsg::NEIGHBOR_VERIFIED);\n+        verified.appendToData(data);\n+        writeData(socket, data);\n+\n+        boost::system::error_code ec;\n+        socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);\n+        if (ec) {\n+            LOGS(_log, LOG_LVL_ERROR, \"ServerTcpBase::testConnect shutdown ec=\" << ec.message());\n+            return false;\n+        }\n+        // socket.close(); socket should close when it falls out of scope.\n+    }\n+    catch (std::exception& e) {\n+        std::cerr << e.what() << std::endl;\n+        return false;\n+    }\n+\n+    return true;\n+}\n+\n+\n+void TcpBaseConnection::start() {\n+    uint32_t ourName = _serverTcpBase->getOurName();\n+    UInt32Element name(ourName);\n+    name.appendToData(_buf);\n+    boost::asio::async_write(_socket, boost::asio::buffer(_buf.getReadCursor(), _buf.getBytesLeftToRead()),\n+                    boost::bind(&TcpBaseConnection::_readKind, shared_from_this(),\n+                            boost::asio::placeholders::error,\n+                            boost::asio::placeholders::bytes_transferred));\n+}\n+\n+\n+void TcpBaseConnection::shutdown() {\n+    boost::system::error_code ec;\n+    _socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);\n+    _socket.close();\n+}\n+\n+\n+void TcpBaseConnection::_freeConnect() {\n+    auto centralW = _serverTcpBase->getCentralWorker();\n+    if (centralW != nullptr) {\n+        centralW->cancelShiftsFromRightNeighbor();\n+    }\n+    _serverTcpBase->freeConnection(shared_from_this());\n+}\n+\n+\n+/// Find out what KIND of message is coming in.\n+void TcpBaseConnection::_readKind(boost::system::error_code const&, size_t /*bytes_transferred*/) {\n+    _buf.reset();\n+\n+    UInt32Element elem;\n+    size_t const bytes = 2*elem.transmitSize(); // uint32 for kind + uint32 for length of message\n+\n+    if (bytes > _buf.getAvailableWriteLength()) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_readKind Buffer would have overflowed\");\n+        _freeConnect();\n+        return;\n+    }\n+\n+    LOGS(_log, LOG_LVL_DEBUG, \"TcpBaseConnection::_readKind _recvKind reset _buf=\" << _buf.dumpStr());\n+    boost::asio::async_read(_socket, boost::asio::buffer(_buf.getWriteCursor(), bytes),\n+            boost::asio::transfer_at_least(bytes),\n+            boost::bind(\n+                    &TcpBaseConnection::_recvKind,\n+                    shared_from_this(),\n+                    boost::asio::placeholders::error,\n+                    boost::asio::placeholders::bytes_transferred\n+            )\n+    );\n+}\n+\n+\n+void TcpBaseConnection::_recvKind(const boost::system::error_code& ec, size_t bytesTrans) {\n+    if (ec) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_recvKind ec=\" << ec);\n+        _freeConnect();\n+        return;\n+    }\n+    // Fix the buffer with the information given.\n+    _buf.advanceWriteCursor(bytesTrans);\n+    auto msgElem = MsgElement::retrieve(_buf);\n+    auto msgKind = std::dynamic_pointer_cast<UInt32Element>(msgElem);\n+    if (msgKind == nullptr) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_recvKind unexpected type of msg\");\n+        _freeConnect();\n+        return;\n+    }\n+    msgElem = MsgElement::retrieve(_buf);\n+    auto msgBytes = std::dynamic_pointer_cast<UInt32Element>(msgElem);\n+    if (msgBytes == nullptr) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_recvKind missing bytes\");\n+        _freeConnect();\n+        return;\n+    }\n+    LOGS(_log, LOG_LVL_INFO, \"_recvKind kind=\" << msgKind->element << \" bytes=\" << msgBytes->element);\n+    switch (msgKind->element) {\n+    case LoaderMsg::IM_YOUR_L_NEIGHBOR:\n+        LOGS(_log, LOG_LVL_INFO, \"_recvKind IM_YOUR_L_NEIGHBOR\");\n+        _handleImYourLNeighbor(msgBytes->element);\n+        break;\n+    case LoaderMsg::SHIFT_TO_RIGHT:\n+        LOGS(_log, LOG_LVL_INFO, \"_recvKind SHIFT_TO_RIGHT our left neighbor is shifting to us\");\n+        _handleShiftToRight(msgBytes->element);\n+        break;\n+    case LoaderMsg::SHIFT_FROM_RIGHT:\n+        LOGS(_log, LOG_LVL_INFO, \"_recvKind SHIFT_FROM_RIGHT our left neighbor needs keys shifted from this\");\n+        _handleShiftFromRight(msgBytes->element);\n+        break;\n+    case LoaderMsg::TEST:\n+        LOGS(_log, LOG_LVL_INFO, \"_recvKind TEST\");\n+        _handleTest();\n+        break;\n+    default:\n+        LOGS(_log, LOG_LVL_ERROR, \"_recvKind unexpected kind=\" << msgKind->element);\n+        _freeConnect();\n+    }\n+}\n+\n+\n+void TcpBaseConnection::_handleTest() {\n+    _buf.reset();\n+\n+    UInt32Element kind;\n+    UInt32Element rNName;\n+    UInt64Element valuePairCount;\n+    size_t bytes = kind.transmitSize() + rNName.transmitSize() + valuePairCount.transmitSize();\n+\n+    if (bytes > _buf.getAvailableWriteLength()) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_handleTest Buffer would have overflowed\");\n+        _freeConnect();\n+        return;\n+    }\n+    boost::asio::async_read(_socket, boost::asio::buffer(_buf.getWriteCursor(), bytes),\n+            boost::asio::transfer_at_least(bytes),\n+            boost::bind(\n+                    &TcpBaseConnection::_handleTest2,\n+                    shared_from_this(),\n+                    boost::asio::placeholders::error,\n+                    boost::asio::placeholders::bytes_transferred\n+            )\n+    );\n+}\n+\n+\n+void TcpBaseConnection::_handleTest2(const boost::system::error_code& ec, size_t bytesTrans) {\n+    if (ec) {\n+         LOGS(_log, LOG_LVL_ERROR, \"_recvKind ec=\" << ec);\n+         _freeConnect();\n+         return;\n+     }\n+     // Fix the buffer with the information given.\n+     _buf.advanceWriteCursor(bytesTrans);\n+     auto msgElem = MsgElement::retrieve(_buf);\n+     auto msgKind = std::dynamic_pointer_cast<UInt32Element>(msgElem);\n+     msgElem = MsgElement::retrieve(_buf);\n+     auto msgName = std::dynamic_pointer_cast<UInt32Element>(msgElem);\n+     msgElem = MsgElement::retrieve(_buf);\n+     auto msgKeys = std::dynamic_pointer_cast<UInt64Element>(msgElem);\n+\n+     // TODO move most of this to CentralWorker\n+     // test that this is the neighbor that was expected.\n+     if (msgKind->element != LoaderMsg::IM_YOUR_R_NEIGHBOR ||\n+         msgName->element != testNewNodeName ||\n+         msgKeys->element != testNewNodeValuePairCount)  {\n+         LOGS(_log, LOG_LVL_ERROR, \"_handleTest2 unexpected element or name\" <<\n+              \" kind=\" << msgKind->element << \" msgName=\" << msgName->element <<\n+              \" keys=\" << msgKeys->element);\n+         _freeConnect();\n+         return;\n+     } else {\n+         LOGS(_log, LOG_LVL_INFO, \"_handleTest2 kind=\" << msgKind->element << \" msgName=\"\n+              << msgName->element << \" keys=\" << msgKeys->element);\n+     }\n+\n+     // send im_left_neighbor message, how many elements we have. If it had zero elements, an element will be sent\n+     // so that new neighbor gets a range.\n+     _buf.reset();\n+     // build the protobuffer\n+     msgKind = std::make_shared<UInt32Element>(LoaderMsg::IM_YOUR_L_NEIGHBOR);\n+     msgKind->appendToData(_buf);\n+     UInt32Element ourName(testOldNodeName);\n+     ourName.appendToData(_buf);\n+     UInt64Element keyCount(testOldNodeKeyCount);\n+     keyCount.appendToData(_buf);\n+     boost::asio::async_write(_socket, boost::asio::buffer(_buf.getReadCursor(), _buf.getBytesLeftToRead()),\n+            boost::bind(&TcpBaseConnection::_handleTest2b, shared_from_this(),\n+              boost::asio::placeholders::error,\n+              boost::asio::placeholders::bytes_transferred));\n+\n+}\n+\n+\n+void TcpBaseConnection::_handleTest2b(const boost::system::error_code& ec, size_t bytesTrans) {\n+    UInt32Element kind;\n+    size_t bytes = kind.transmitSize();\n+    _buf.reset();\n+    boost::asio::async_read(_socket, boost::asio::buffer(_buf.getWriteCursor(), bytes),\n+            boost::asio::transfer_at_least(bytes),\n+            boost::bind(\n+                    &TcpBaseConnection::_handleTest2c,\n+                    shared_from_this(),\n+                    boost::asio::placeholders::error,\n+                    boost::asio::placeholders::bytes_transferred\n+            )\n+    );\n+}\n+\n+\n+void TcpBaseConnection::_handleTest2c(const boost::system::error_code& ec, size_t bytesTrans) {\n+    if (ec) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_recvKind ec=\" << ec);\n+        _freeConnect();\n+        return;\n+    }\n+    // Fix the buffer with the information given.\n+    _buf.advanceWriteCursor(bytesTrans);\n+    auto msgElem = MsgElement::retrieve(_buf);\n+    if (msgElem == nullptr) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_handleTest2b Kind nullptr error\");\n+        _freeConnect();\n+        return;\n+    }\n+    auto msgKind = std::dynamic_pointer_cast<UInt32Element>(msgElem);\n+    if (msgKind != nullptr && msgKind->element != LoaderMsg::NEIGHBOR_VERIFIED) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_handleTest2b NEIGHBOR_VERIFIED error\" <<\n+                \" kind=\" << msgKind->element);\n+        _freeConnect();\n+        return;\n+    }\n+    LOGS(_log, LOG_LVL_INFO, \"TcpBaseConnection::_handleTest SUCCESS\");\n+    _freeConnect(); // Close the connection at the end of the test.\n+}\n+\n+\n+void TcpBaseConnection::_handleImYourLNeighbor(uint32_t bytesInMsg) {\n+    // Need to figure out the difference between bytes read and bytes in _buf\n+    if (bytesInMsg > _buf.getAvailableWriteLength()) {\n+        LOGS(_log, LOG_LVL_ERROR, \"_handleImYourLNeighbor Buffer would have overflowed\");\n+        _freeConnect();\n+        return;\n+    }\n+    LOGS(_log, LOG_LVL_INFO, \"_handleImYourLNeighbor bytes=\" << bytesInMsg <<\n+                             \" buf=\" << _buf.dumpStr(false));\n+    boost::asio::async_read(_socket, boost::asio::buffer(_buf.getWriteCursor(), bytesInMsg),\n+            boost::asio::transfer_at_least(bytesInMsg),\n+            boost::bind(\n+                    &TcpBaseConnection::_handleImYourLNeighbor1,\n+                    shared_from_this(),\n+                    boost::asio::placeholders::error,\n+                    boost::asio::placeholders::bytes_transferred\n+            )\n+    );\n+}\n+\n+void TcpBaseConnection::_handleImYourLNeighbor1(boost::system::error_code const& ec, size_t bytesTrans) {\n+    std::string const funcName = \"_handleImYourLNeighbor1\";\n+    if (ec) {\n+        LOGS(_log, LOG_LVL_ERROR, funcName << \" ec=\" << ec);\n+        _freeConnect();\n+        return;\n+    }\n+    // Fix the buffer with the information given.\n+    _buf.advanceWriteCursor(bytesTrans);\n+    LOGS(_log, LOG_LVL_INFO, funcName << \" bytes=\" << bytesTrans << \" _buf\" << _buf.dumpStr(false));\n+    try {\n+        // TODO move as much of this to CentralWorker as possible\n+        // Parse left neighbor's key and range information.\n+        LOGS(_log, LOG_LVL_INFO, funcName <<  \" parsing bytes=\" << bytesTrans <<\n+                                 \" _buf\" << _buf.dumpStr(false));\n+        auto protoItem = StringElement::protoParse<proto::WorkerKeysInfo>(_buf);\n+        if (protoItem == nullptr) {\n+            throw LoaderMsgErr(ERR_LOC, \"protoItem nullptr\");\n+        }\n+        NeighborsInfo nInfo;\n+        auto workerName = protoItem->wid();\n+        nInfo.keyCount = protoItem->mapsize();\n+        nInfo.recentAdds = protoItem->recentadds();\n+        proto::WorkerRangeString protoRange = protoItem->range();\n+        LOGS(_log, LOG_LVL_INFO, funcName << \" WorkerKeysInfo name=\" << workerName <<\n+                                 \" keyCount=\" << nInfo.keyCount << \" recentAdds=\" << nInfo.recentAdds);\n+        bool valid = protoRange.valid();\n+        StringRange leftRange;\n+        StringRange newLeftRange;\n+        if (valid) {\n+            std::string min   = protoRange.min();\n+            std::string max   = protoRange.max();\n+            bool unlimited = protoRange.maxunlimited();\n+            leftRange.setMinMax(min, max, unlimited);\n+            LOGS(_log, LOG_LVL_WARN, funcName << \" leftRange=\" << leftRange);\n+            newLeftRange = _serverTcpBase->getCentralWorker()->updateRangeWithLeftData(leftRange);\n+        }\n+        proto::Neighbor protoLeftNeigh = protoItem->left();\n+        nInfo.neighborLeft->update(protoLeftNeigh.wid());  // Not really useful in this case.\n+        proto::Neighbor protoRightNeigh = protoItem->right();\n+        nInfo.neighborRight->update(protoRightNeigh.wid()); // This should be our name\n+        if (nInfo.neighborRight->get() != _serverTcpBase->getOurName()) {\n+            LOGS(_log, LOG_LVL_ERROR, \"Our (\" << _serverTcpBase->getOurName() <<\n+                                      \") left neighbor does not have our name as its right neighbor\" );\n+        }\n+\n+        _serverTcpBase->getCentralWorker()->setNeighborInfoLeft(workerName, nInfo.keyCount, newLeftRange);\n+\n+        // Need to send our range and key count back to left neighbor so it can figure out what to do with its range.\n+        _buf.reset();\n+        StringElement strWKI;\n+        std::unique_ptr<proto::WorkerKeysInfo> protoWKI = _serverTcpBase->getCentralWorker()->_workerKeysInfoBuilder();\n+        protoWKI->SerializeToString(&(strWKI.element));\n+        UInt32Element bytesInMsg(strWKI.transmitSize());\n+        // Send the number of bytes in the message so TCP client knows how many bytes to read.\n+        bytesInMsg.appendToData(_buf);\n+        strWKI.appendToData(_buf);\n+        ServerTcpBase::writeData(_socket, _buf);\n+        LOGS(_log, LOG_LVL_INFO, funcName << \" done\");\n+    } catch (LoaderMsgErr &msgErr) {\n+        LOGS(_log, LOG_LVL_ERROR, funcName << \" Buffer failed \" << msgErr.what());\n+        _freeConnect();\n+        return;\n+    }\n+    boost::system::error_code ecode;\n+    _readKind(ecode, 0); // get next message","path":"core/modules/loader/ServerTcpBase.cc","position":465,"original_position":465,"commit_id":"97f66602dd7a0ae7f6d46808b951099bbc5b649a","original_commit_id":"c9ef0a9991cfe1464425ad1d2394505360505336","user":{"login":"n8pease","id":13370548,"node_id":"MDQ6VXNlcjEzMzcwNTQ4","avatar_url":"https://avatars0.githubusercontent.com/u/13370548?v=4","gravatar_id":"","url":"https://api.github.com/users/n8pease","html_url":"https://github.com/n8pease","followers_url":"https://api.github.com/users/n8pease/followers","following_url":"https://api.github.com/users/n8pease/following{/other_user}","gists_url":"https://api.github.com/users/n8pease/gists{/gist_id}","starred_url":"https://api.github.com/users/n8pease/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/n8pease/subscriptions","organizations_url":"https://api.github.com/users/n8pease/orgs","repos_url":"https://api.github.com/users/n8pease/repos","events_url":"https://api.github.com/users/n8pease/events{/privacy}","received_events_url":"https://api.github.com/users/n8pease/received_events","type":"User","site_admin":false},"body":"@jgates108 It would be helpful for me coming through as 2nd reviewer if there were answers or comments on these questions.","created_at":"2018-11-30T22:46:06Z","updated_at":"2018-11-30T22:46:07Z","html_url":"https://github.com/lsst/qserv/pull/426#discussion_r238025796","pull_request_url":"https://api.github.com/repos/lsst/qserv/pulls/426","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/lsst/qserv/pulls/comments/238025796"},"html":{"href":"https://github.com/lsst/qserv/pull/426#discussion_r238025796"},"pull_request":{"href":"https://api.github.com/repos/lsst/qserv/pulls/426"}},"in_reply_to_id":233743354},"pull_request":{"url":"https://api.github.com/repos/lsst/qserv/pulls/426","id":227734393,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI3NzM0Mzkz","html_url":"https://github.com/lsst/qserv/pull/426","diff_url":"https://github.com/lsst/qserv/pull/426.diff","patch_url":"https://github.com/lsst/qserv/pull/426.patch","issue_url":"https://api.github.com/repos/lsst/qserv/issues/426","number":426,"state":"open","locked":false,"title":"U/jgates/loader","user":{"login":"jgates108","id":6558057,"node_id":"MDQ6VXNlcjY1NTgwNTc=","avatar_url":"https://avatars1.githubusercontent.com/u/6558057?v=4","gravatar_id":"","url":"https://api.github.com/users/jgates108","html_url":"https://github.com/jgates108","followers_url":"https://api.github.com/users/jgates108/followers","following_url":"https://api.github.com/users/jgates108/following{/other_user}","gists_url":"https://api.github.com/users/jgates108/gists{/gist_id}","starred_url":"https://api.github.com/users/jgates108/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgates108/subscriptions","organizations_url":"https://api.github.com/users/jgates108/orgs","repos_url":"https://api.github.com/users/jgates108/repos","events_url":"https://api.github.com/users/jgates108/events{/privacy}","received_events_url":"https://api.github.com/users/jgates108/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-11-01T19:36:18Z","updated_at":"2018-11-30T22:46:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ebb16085fe1da45fb682e5a71e45035959af8a57","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/lsst/qserv/pulls/426/commits","review_comments_url":"https://api.github.com/repos/lsst/qserv/pulls/426/comments","review_comment_url":"https://api.github.com/repos/lsst/qserv/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lsst/qserv/issues/426/comments","statuses_url":"https://api.github.com/repos/lsst/qserv/statuses/97f66602dd7a0ae7f6d46808b951099bbc5b649a","head":{"label":"lsst:u/jgates/loader","ref":"u/jgates/loader","sha":"97f66602dd7a0ae7f6d46808b951099bbc5b649a","user":{"login":"lsst","id":1134504,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExMzQ1MDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1134504?v=4","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"repo":{"id":23013460,"node_id":"MDEwOlJlcG9zaXRvcnkyMzAxMzQ2MA==","name":"qserv","full_name":"lsst/qserv","private":false,"owner":{"login":"lsst","id":1134504,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExMzQ1MDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1134504?v=4","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/lsst/qserv","description":"LSST Query Services","fork":false,"url":"https://api.github.com/repos/lsst/qserv","forks_url":"https://api.github.com/repos/lsst/qserv/forks","keys_url":"https://api.github.com/repos/lsst/qserv/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst/qserv/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst/qserv/teams","hooks_url":"https://api.github.com/repos/lsst/qserv/hooks","issue_events_url":"https://api.github.com/repos/lsst/qserv/issues/events{/number}","events_url":"https://api.github.com/repos/lsst/qserv/events","assignees_url":"https://api.github.com/repos/lsst/qserv/assignees{/user}","branches_url":"https://api.github.com/repos/lsst/qserv/branches{/branch}","tags_url":"https://api.github.com/repos/lsst/qserv/tags","blobs_url":"https://api.github.com/repos/lsst/qserv/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst/qserv/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst/qserv/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst/qserv/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst/qserv/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst/qserv/languages","stargazers_url":"https://api.github.com/repos/lsst/qserv/stargazers","contributors_url":"https://api.github.com/repos/lsst/qserv/contributors","subscribers_url":"https://api.github.com/repos/lsst/qserv/subscribers","subscription_url":"https://api.github.com/repos/lsst/qserv/subscription","commits_url":"https://api.github.com/repos/lsst/qserv/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst/qserv/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst/qserv/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst/qserv/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst/qserv/contents/{+path}","compare_url":"https://api.github.com/repos/lsst/qserv/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst/qserv/merges","archive_url":"https://api.github.com/repos/lsst/qserv/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst/qserv/downloads","issues_url":"https://api.github.com/repos/lsst/qserv/issues{/number}","pulls_url":"https://api.github.com/repos/lsst/qserv/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst/qserv/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst/qserv/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst/qserv/labels{/name}","releases_url":"https://api.github.com/repos/lsst/qserv/releases{/id}","deployments_url":"https://api.github.com/repos/lsst/qserv/deployments","created_at":"2014-08-16T07:50:36Z","updated_at":"2018-11-26T22:12:30Z","pushed_at":"2018-11-29T23:42:15Z","git_url":"git://github.com/lsst/qserv.git","ssh_url":"git@github.com:lsst/qserv.git","clone_url":"https://github.com/lsst/qserv.git","svn_url":"https://github.com/lsst/qserv","homepage":"http://dm.lsst.org/","size":20754,"stargazers_count":19,"watchers_count":19,"language":"C++","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":16,"mirror_url":null,"archived":false,"open_issues_count":4,"license":null,"forks":16,"open_issues":4,"watchers":19,"default_branch":"master"}},"base":{"label":"lsst:master","ref":"master","sha":"6cc5ca990438e19a66c5b8e03d6f39d0eaa2a391","user":{"login":"lsst","id":1134504,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExMzQ1MDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1134504?v=4","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"repo":{"id":23013460,"node_id":"MDEwOlJlcG9zaXRvcnkyMzAxMzQ2MA==","name":"qserv","full_name":"lsst/qserv","private":false,"owner":{"login":"lsst","id":1134504,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExMzQ1MDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1134504?v=4","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/lsst/qserv","description":"LSST Query Services","fork":false,"url":"https://api.github.com/repos/lsst/qserv","forks_url":"https://api.github.com/repos/lsst/qserv/forks","keys_url":"https://api.github.com/repos/lsst/qserv/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst/qserv/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst/qserv/teams","hooks_url":"https://api.github.com/repos/lsst/qserv/hooks","issue_events_url":"https://api.github.com/repos/lsst/qserv/issues/events{/number}","events_url":"https://api.github.com/repos/lsst/qserv/events","assignees_url":"https://api.github.com/repos/lsst/qserv/assignees{/user}","branches_url":"https://api.github.com/repos/lsst/qserv/branches{/branch}","tags_url":"https://api.github.com/repos/lsst/qserv/tags","blobs_url":"https://api.github.com/repos/lsst/qserv/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst/qserv/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst/qserv/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst/qserv/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst/qserv/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst/qserv/languages","stargazers_url":"https://api.github.com/repos/lsst/qserv/stargazers","contributors_url":"https://api.github.com/repos/lsst/qserv/contributors","subscribers_url":"https://api.github.com/repos/lsst/qserv/subscribers","subscription_url":"https://api.github.com/repos/lsst/qserv/subscription","commits_url":"https://api.github.com/repos/lsst/qserv/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst/qserv/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst/qserv/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst/qserv/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst/qserv/contents/{+path}","compare_url":"https://api.github.com/repos/lsst/qserv/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst/qserv/merges","archive_url":"https://api.github.com/repos/lsst/qserv/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst/qserv/downloads","issues_url":"https://api.github.com/repos/lsst/qserv/issues{/number}","pulls_url":"https://api.github.com/repos/lsst/qserv/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst/qserv/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst/qserv/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst/qserv/labels{/name}","releases_url":"https://api.github.com/repos/lsst/qserv/releases{/id}","deployments_url":"https://api.github.com/repos/lsst/qserv/deployments","created_at":"2014-08-16T07:50:36Z","updated_at":"2018-11-26T22:12:30Z","pushed_at":"2018-11-29T23:42:15Z","git_url":"git://github.com/lsst/qserv.git","ssh_url":"git@github.com:lsst/qserv.git","clone_url":"https://github.com/lsst/qserv.git","svn_url":"https://github.com/lsst/qserv","homepage":"http://dm.lsst.org/","size":20754,"stargazers_count":19,"watchers_count":19,"language":"C++","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":16,"mirror_url":null,"archived":false,"open_issues_count":4,"license":null,"forks":16,"open_issues":4,"watchers":19,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lsst/qserv/pulls/426"},"html":{"href":"https://github.com/lsst/qserv/pull/426"},"issue":{"href":"https://api.github.com/repos/lsst/qserv/issues/426"},"comments":{"href":"https://api.github.com/repos/lsst/qserv/issues/426/comments"},"review_comments":{"href":"https://api.github.com/repos/lsst/qserv/pulls/426/comments"},"review_comment":{"href":"https://api.github.com/repos/lsst/qserv/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lsst/qserv/pulls/426/commits"},"statuses":{"href":"https://api.github.com/repos/lsst/qserv/statuses/97f66602dd7a0ae7f6d46808b951099bbc5b649a"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"n8pease"}}