{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/qserv/pulls/comments/192273613","pull_request_review_id":125018714,"id":192273613,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5MjI3MzYxMw==","diff_hunk":"@@ -0,0 +1,543 @@\n+/*\n+ * LSST Data Management System\n+ * Copyright 2017 LSST Corporation.\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+// Class header\n+#include \"replica/WorkerServerConnection.h\"\n+\n+// System headers\n+\n+// Third party headers\n+#include <boost/bind.hpp>\n+\n+// Qserv headers\n+#include \"lsst/log/Log.h\"\n+#include \"replica/Configuration.h\"\n+#include \"replica/Performance.h\"\n+#include \"replica/ServiceProvider.h\"\n+#include \"replica/WorkerProcessor.h\"\n+\n+namespace {\n+\n+LOG_LOGGER _log = LOG_GET(\"lsst.qserv.replica.WorkerServerConnection\");\n+\n+} /// namespace\n+\n+namespace {\n+\n+using ProtocolBufferPtr = std::shared_ptr<lsst::qserv::replica::ProtocolBuffer>;\n+\n+/// The context for diagnostic & debug printouts\n+std::string const context = \"CONNECTION  \";\n+\n+bool isErrorCode(boost::system::error_code const& ec,\n+                 std::string const& scope) {\n+\n+    if (ec) {\n+        if (ec == boost::asio::error::eof) {\n+            LOGS(_log, LOG_LVL_DEBUG, context << scope << \"  ** closed **\");\n+        } else {\n+            LOGS(_log, LOG_LVL_ERROR, context << scope << \"  ** failed: \" << ec << \" **\");\n+        }\n+        return true;\n+    }\n+    return false;\n+}\n+\n+bool readIntoBuffer(boost::asio::ip::tcp::socket& socket,\n+                    ProtocolBufferPtr const& ptr,\n+                    size_t bytes) {\n+\n+    ptr->resize(bytes);     // make sure the buffer has enough space to accomodate\n+                            // the data of the message.\n+\n+    boost::system::error_code ec;\n+    boost::asio::read(\n+        socket,\n+        boost::asio::buffer(\n+            ptr->data(),\n+            bytes\n+        ),\n+        boost::asio::transfer_at_least(bytes),\n+        ec\n+    );\n+    return not ::isErrorCode(ec, \"readIntoBuffer\");\n+}\n+\n+template <class T>\n+bool readMessage(boost::asio::ip::tcp::socket& socket,\n+                 ProtocolBufferPtr const& ptr,\n+                 size_t bytes,\n+                 T& message) {\n+\n+    if (not readIntoBuffer(socket,\n+                           ptr,\n+                           bytes)) return false;\n+\n+    // Parse the response to see what should be done next.\n+\n+    ptr->parse(message, bytes);\n+    return true;\n+}\n+\n+bool readLength(boost::asio::ip::tcp::socket& socket,\n+                ProtocolBufferPtr const& ptr,\n+                uint32_t& bytes) {\n+\n+    if (not readIntoBuffer(socket,\n+                           ptr,\n+                           sizeof(uint32_t))) return false;\n+\n+    bytes = ptr->parseLength();\n+    return true;\n+}\n+}   // namespace\n+\n+namespace lsst {\n+namespace qserv {\n+namespace replica {\n+\n+WorkerServerConnection::Ptr WorkerServerConnection::create(\n+                                    ServiceProvider::Ptr const& serviceProvider,\n+                                    WorkerProcessor& processor,\n+                                    boost::asio::io_service& io_service) {\n+    return WorkerServerConnection::Ptr(\n+        new WorkerServerConnection(\n+            serviceProvider,\n+            processor,\n+            io_service));\n+}\n+\n+WorkerServerConnection::WorkerServerConnection(ServiceProvider::Ptr const& serviceProvider,\n+                                               WorkerProcessor& processor,\n+                                               boost::asio::io_service& io_service)\n+    :   _serviceProvider(serviceProvider),\n+        _processor(processor),\n+        _socket(io_service),\n+        _bufferPtr(std::make_shared<ProtocolBuffer>(\n+                       serviceProvider->config()->requestBufferSizeBytes())) {\n+}\n+\n+void WorkerServerConnection::beginProtocol() {\n+    receive();\n+}\n+\n+void WorkerServerConnection::receive() {\n+\n+    LOGS(_log, LOG_LVL_DEBUG, context << \"receive\");\n+\n+    // Start with receiving the fixed length frame carrying\n+    // the size (in bytes) the length of the subsequent message.\n+    //\n+    // The message itself will be read from the handler using\n+    // the synchronous read method. This is based on an assumption\n+    // that the worker server sends the whol emessage (its frame and\n+    // the message itsef) at once.\n+\n+    const size_t bytes = sizeof(uint32_t);\n+\n+    _bufferPtr->resize(bytes);\n+\n+    boost::asio::async_read(\n+        _socket,\n+        boost::asio::buffer(\n+            _bufferPtr->data(),\n+            bytes\n+        ),\n+        boost::asio::transfer_at_least(bytes),\n+        boost::bind(\n+            &WorkerServerConnection::received,\n+            shared_from_this(),\n+            boost::asio::placeholders::error,\n+            boost::asio::placeholders::bytes_transferred\n+        )\n+    );\n+}\n+\n+void WorkerServerConnection::received(boost::system::error_code const& ec,\n+                                      size_t bytes_transferred) {\n+\n+    LOGS(_log, LOG_LVL_DEBUG, context << \"received\");\n+\n+    if (::isErrorCode(ec, \"received\")) { return; }\n+\n+    // Now read the request header\n+\n+    proto::ReplicationRequestHeader hdr;\n+    if (not ::readMessage(_socket,\n+                          _bufferPtr,\n+                          _bufferPtr->parseLength(),\n+                          hdr)) return;\n+\n+    // Analyse the header of the request. Note that the header message categorizes\n+    // requests in two layers:\n+    // - first goes the class of requests as defined by member 'type'\n+    // - then  goes a choice of a specific request within its class. Those specific\n+    //   request codes are obtained from the corresponding members\n+\n+    switch (hdr.type()) {\n+\n+        case proto::ReplicationRequestHeader::REPLICA: processReplicaRequest(   hdr); break;\n+        case proto::ReplicationRequestHeader::REQUEST: processManagementRequest(hdr); break;\n+        case proto::ReplicationRequestHeader::SERVICE: processServiceRequest(   hdr); break;\n+\n+        default:\n+            throw std::logic_error(\n+                  \"WorkerServerConnection::received() unhandled request class: '\" +\n+                  proto::ReplicationRequestHeader::RequestType_Name(hdr.type()));\n+    }\n+}\n+\n+void WorkerServerConnection::processReplicaRequest(proto::ReplicationRequestHeader& hdr) {\n+\n+    // Read the request length\n+    uint32_t bytes;\n+    if (not ::readLength (_socket,\n+                          _bufferPtr,\n+                          bytes)) return;\n+\n+    switch (hdr.replica_type()) {\n+\n+        case proto::ReplicationReplicaRequestType::REPLICA_CREATE: {\n+\n+            // Read the request body\n+            proto::ReplicationRequestReplicate request;\n+            if (not ::readMessage(_socket,\n+                                  _bufferPtr,\n+                                  bytes,\n+                                  request)) return;\n+\n+            proto::ReplicationResponseReplicate response;\n+            _processor.enqueueForReplication(hdr.id(),\n+                                             request,\n+                                             response);\n+            reply(hdr.id(),\n+                  response);\n+\n+            break;\n+        }\n+        case proto::ReplicationReplicaRequestType::REPLICA_DELETE: {\n+\n+            // Read the request body\n+            proto::ReplicationRequestDelete request;\n+            if (not ::readMessage(_socket,\n+                                  _bufferPtr,\n+                                  bytes,\n+                                  request)) return;\n+\n+            proto::ReplicationResponseDelete response;\n+            _processor.enqueueForDeletion(hdr.id(),\n+                                          request,\n+                                          response);\n+            reply(hdr.id(),\n+                  response);\n+\n+            break;\n+        }\n+        case proto::ReplicationReplicaRequestType::REPLICA_FIND: {\n+\n+            // Read the request body\n+            proto::ReplicationRequestFind request;\n+            if (not ::readMessage(_socket,\n+                                  _bufferPtr,\n+                                  bytes,\n+                                  request)) return;\n+\n+            proto::ReplicationResponseFind response;\n+            _processor.enqueueForFind(hdr.id(),\n+                                      request,\n+                                      response);\n+            reply(hdr.id(),\n+                  response);\n+\n+            break;\n+        }\n+        case proto::ReplicationReplicaRequestType::REPLICA_FIND_ALL: {\n+\n+            // Read the request body\n+            proto::ReplicationRequestFindAll request;\n+            if (not ::readMessage(_socket,\n+                                  _bufferPtr,\n+                                  bytes,\n+                                  request)) return;\n+\n+            proto::ReplicationResponseFindAll response;\n+            _processor.enqueueForFindAll(hdr.id(),\n+                                         request,\n+                                         response);\n+            reply(hdr.id(),\n+                  response);\n+\n+            break;\n+        }\n+        default:\n+            throw std::logic_error(\n+                  \"WorkerServerConnection::processReplicaRequest() unhandled request type: '\" +\n+                  proto::ReplicationReplicaRequestType_Name(hdr.replica_type()));\n+    }\n+}\n+\n+void WorkerServerConnection::processManagementRequest(proto::ReplicationRequestHeader& hdr) {\n+\n+    // Read the request length\n+    uint32_t bytes;\n+    if (not ::readLength (_socket,\n+                          _bufferPtr,\n+                          bytes)) return;\n+\n+    switch (hdr.management_type()) {\n+\n+        case proto::ReplicationManagementRequestType::REQUEST_STOP: {\n+\n+            // Read the request body\n+            proto::ReplicationRequestStop request;\n+            if (not ::readMessage(_socket,\n+                                  _bufferPtr,\n+                                  bytes,\n+                                  request)) return;\n+\n+            switch (request.type()) {\n+\n+                case proto::ReplicationReplicaRequestType::REPLICA_CREATE: {\n+                    proto::ReplicationResponseReplicate response;\n+                    _processor.dequeueOrCancel(hdr.id(),\n+                                               request,\n+                                               response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+\n+                case proto::ReplicationReplicaRequestType::REPLICA_DELETE: {\n+                    proto::ReplicationResponseDelete response;\n+                    _processor.dequeueOrCancel(hdr.id(),\n+                                               request,\n+                                               response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+                case proto::ReplicationReplicaRequestType::REPLICA_FIND: {\n+                    proto::ReplicationResponseFind response;\n+                    _processor.dequeueOrCancel(hdr.id(),\n+                                               request,\n+                                               response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+                case proto::ReplicationReplicaRequestType::REPLICA_FIND_ALL: {\n+                    proto::ReplicationResponseFindAll response;\n+                    _processor.dequeueOrCancel(hdr.id(),\n+                                               request,\n+                                               response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+            }\n+            break;\n+        }\n+        case proto::ReplicationManagementRequestType::REQUEST_STATUS : {\n+\n+            // Read the request body\n+            proto::ReplicationRequestStatus request;\n+            if (not ::readMessage(_socket,\n+                                  _bufferPtr,\n+                                  bytes,\n+                                  request)) return;\n+\n+            switch (request.type()) {\n+\n+                case proto::ReplicationReplicaRequestType::REPLICA_CREATE: {\n+                    proto::ReplicationResponseReplicate response;\n+                    _processor.checkStatus(hdr.id(),\n+                                           request,\n+                                           response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+                case proto::ReplicationReplicaRequestType::REPLICA_DELETE: {\n+                    proto::ReplicationResponseDelete response;\n+                    _processor.checkStatus(hdr.id(),\n+                                           request,\n+                                           response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+                case proto::ReplicationReplicaRequestType::REPLICA_FIND: {\n+                    proto::ReplicationResponseFind response;\n+                    _processor.checkStatus(hdr.id(),\n+                                           request,\n+                                           response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+                case proto::ReplicationReplicaRequestType::REPLICA_FIND_ALL: {\n+                    proto::ReplicationResponseFindAll response;\n+                    _processor.checkStatus(hdr.id(),\n+                                           request,\n+                                           response);\n+                    reply(hdr.id(),\n+                          response);\n+                    break;\n+                }\n+            }\n+            break;\n+        }\n+        default:\n+            throw std::logic_error(\n+                  \"WorkerServerConnection::processManagementRequest() unhandled request type: '\" +\n+                  proto::ReplicationManagementRequestType_Name(hdr.management_type()));\n+    }\n+}\n+\n+void WorkerServerConnection::processServiceRequest(proto::ReplicationRequestHeader& hdr) {\n+\n+    proto::ReplicationServiceResponse response;\n+\n+    // All performance counters for this type of requests should be\n+    // equal because this is the instantenous request\n+\n+    WorkerPerformance performance;\n+    performance.setUpdateStart();\n+    performance.setUpdateFinish();\n+    response.set_allocated_performance(performance.info());\n+\n+    switch (hdr.service_type()) {\n+\n+        case proto::ReplicationServiceRequestType::SERVICE_SUSPEND: {\n+\n+            // This operation is allowed to be asynchronious as it may take\n+            // extra time for the processor's threads to finish on-going processing\n+\n+            _processor.stop();\n+            _processor.setServiceResponse(\n+                  response,\n+                  hdr.id(),\n+                  _processor.state() == WorkerProcessor::State::STATE_IS_RUNNING ?\n+                      proto::ReplicationServiceResponse::FAILED :\n+                      proto::ReplicationServiceResponse::SUCCESS);","path":"core/modules/replica/WorkerServerConnection.cc","position":441,"original_position":441,"commit_id":"d3793dc365b3079ec1ca4c3ca45ef10211abb69d","original_commit_id":"d3793dc365b3079ec1ca4c3ca45ef10211abb69d","user":{"login":"iagaponenko","id":14188390,"node_id":"MDQ6VXNlcjE0MTg4Mzkw","avatar_url":"https://avatars3.githubusercontent.com/u/14188390?v=4","gravatar_id":"","url":"https://api.github.com/users/iagaponenko","html_url":"https://github.com/iagaponenko","followers_url":"https://api.github.com/users/iagaponenko/followers","following_url":"https://api.github.com/users/iagaponenko/following{/other_user}","gists_url":"https://api.github.com/users/iagaponenko/gists{/gist_id}","starred_url":"https://api.github.com/users/iagaponenko/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iagaponenko/subscriptions","organizations_url":"https://api.github.com/users/iagaponenko/orgs","repos_url":"https://api.github.com/users/iagaponenko/repos","events_url":"https://api.github.com/users/iagaponenko/events{/privacy}","received_events_url":"https://api.github.com/users/iagaponenko/received_events","type":"User","site_admin":false},"body":"It looks good to me. but I made a minor improvement which may help others to read the statement.","created_at":"2018-06-01T00:34:03Z","updated_at":"2018-06-01T00:34:03Z","html_url":"https://github.com/lsst/qserv/pull/393#discussion_r192273613","pull_request_url":"https://api.github.com/repos/lsst/qserv/pulls/393","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/lsst/qserv/pulls/comments/192273613"},"html":{"href":"https://github.com/lsst/qserv/pull/393#discussion_r192273613"},"pull_request":{"href":"https://api.github.com/repos/lsst/qserv/pulls/393"}},"in_reply_to_id":192179095},"pull_request":{"url":"https://api.github.com/repos/lsst/qserv/pulls/393","id":185042944,"node_id":"MDExOlB1bGxSZXF1ZXN0MTg1MDQyOTQ0","html_url":"https://github.com/lsst/qserv/pull/393","diff_url":"https://github.com/lsst/qserv/pull/393.diff","patch_url":"https://github.com/lsst/qserv/pull/393.patch","issue_url":"https://api.github.com/repos/lsst/qserv/issues/393","number":393,"state":"open","locked":false,"title":"Tickets/dm 10424","user":{"login":"iagaponenko","id":14188390,"node_id":"MDQ6VXNlcjE0MTg4Mzkw","avatar_url":"https://avatars3.githubusercontent.com/u/14188390?v=4","gravatar_id":"","url":"https://api.github.com/users/iagaponenko","html_url":"https://github.com/iagaponenko","followers_url":"https://api.github.com/users/iagaponenko/followers","following_url":"https://api.github.com/users/iagaponenko/following{/other_user}","gists_url":"https://api.github.com/users/iagaponenko/gists{/gist_id}","starred_url":"https://api.github.com/users/iagaponenko/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iagaponenko/subscriptions","organizations_url":"https://api.github.com/users/iagaponenko/orgs","repos_url":"https://api.github.com/users/iagaponenko/repos","events_url":"https://api.github.com/users/iagaponenko/events{/privacy}","received_events_url":"https://api.github.com/users/iagaponenko/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-04-30T19:29:59Z","updated_at":"2018-06-01T00:34:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"dd395ae2d1b28603b4565376b6fbff8390fab763","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/lsst/qserv/pulls/393/commits","review_comments_url":"https://api.github.com/repos/lsst/qserv/pulls/393/comments","review_comment_url":"https://api.github.com/repos/lsst/qserv/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lsst/qserv/issues/393/comments","statuses_url":"https://api.github.com/repos/lsst/qserv/statuses/d3793dc365b3079ec1ca4c3ca45ef10211abb69d","head":{"label":"lsst:tickets/DM-10424","ref":"tickets/DM-10424","sha":"d3793dc365b3079ec1ca4c3ca45ef10211abb69d","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","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},"private":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-05-31T01:47:02Z","pushed_at":"2018-05-31T19:28:23Z","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":19797,"stargazers_count":18,"watchers_count":18,"language":"C++","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":13,"mirror_url":null,"archived":false,"open_issues_count":10,"license":null,"forks":13,"open_issues":10,"watchers":18,"default_branch":"master"}},"base":{"label":"lsst:master","ref":"master","sha":"010f04c04afaea73c0f182b0d65dd981896f8898","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","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},"private":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-05-31T01:47:02Z","pushed_at":"2018-05-31T19:28:23Z","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":19797,"stargazers_count":18,"watchers_count":18,"language":"C++","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":13,"mirror_url":null,"archived":false,"open_issues_count":10,"license":null,"forks":13,"open_issues":10,"watchers":18,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lsst/qserv/pulls/393"},"html":{"href":"https://github.com/lsst/qserv/pull/393"},"issue":{"href":"https://api.github.com/repos/lsst/qserv/issues/393"},"comments":{"href":"https://api.github.com/repos/lsst/qserv/issues/393/comments"},"review_comments":{"href":"https://api.github.com/repos/lsst/qserv/pulls/393/comments"},"review_comment":{"href":"https://api.github.com/repos/lsst/qserv/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lsst/qserv/pulls/393/commits"},"statuses":{"href":"https://api.github.com/repos/lsst/qserv/statuses/d3793dc365b3079ec1ca4c3ca45ef10211abb69d"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"iagaponenko"}}