{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/qserv/pulls/comments/238013028","pull_request_review_id":180463304,"id":238013028,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzODAxMzAyOA==","diff_hunk":"@@ -0,0 +1,424 @@\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/MWorkerList.h\"\n+\n+// System headers\n+#include <boost/asio.hpp>\n+#include <iostream>\n+\n+// Third-party headers\n+\n+\n+// qserv headers\n+#include \"loader/CentralMaster.h\"\n+#include \"loader/LoaderMsg.h\"\n+#include \"proto/ProtoImporter.h\"\n+#include \"proto/loader.pb.h\"\n+\n+\n+// LSST headers\n+#include \"lsst/log/Log.h\"\n+\n+namespace {\n+LOG_LOGGER _log = LOG_GET(\"lsst.qserv.loader.MWorkerList\");\n+}\n+\n+namespace lsst {\n+namespace qserv {\n+namespace loader {\n+\n+/// Create commands to set a worker's neighbor.\n+/// It should keep trying this until it works. When the worker sets the neighbor to\n+/// the target value, this object should initiate a chain reaction that destroys itself.\n+/// It is very important that the message and neighborPtr both point to\n+//  the same (left or right) neighbor.\n+class SetNeighborOneShot : public DoListItem, public UpdateNotify<uint32_t> {\n+public:\n+    using Ptr = std::shared_ptr<SetNeighborOneShot>;\n+\n+    static Ptr create(CentralMaster* central_,\n+               MWorkerListItem::Ptr const& target_,\n+               int msg_,\n+               uint32_t neighborName_,\n+               NeighborsInfo::NeighborPtr const& neighborPtr_) {\n+        Ptr oneShot(new SetNeighborOneShot(central_, target_, msg_, neighborName_,  neighborPtr_));\n+        auto oneShotPtr = std::static_pointer_cast<SetNeighborOneShot>(oneShot->getDoListItemPtr());\n+        auto updatePtr = std::static_pointer_cast<UpdateNotify<uint32_t>>(oneShotPtr);\n+        neighborPtr_->registerNotify(updatePtr); // Must do this so it will call our updateNotify().\n+        LOGS(_log, LOG_LVL_INFO, \"SetNeighborOneShot neighborName=\" <<\n+                                 neighborName_ << \" \" << neighborPtr_->get());\n+        return oneShot;\n+    }\n+\n+    util::CommandTracked::Ptr createCommand() override;\n+\n+    // This is called every time the worker sends the master a value for its (left/right) neighbor.\n+    // See neighborPtr_->registerNotify()\n+    void updateNotify(uint32_t& oldVal, uint32_t& newVal) override {\n+        if (newVal == neighborName) {\n+            infoReceived(); // This should result in this oneShot DoListItem being removed->destroyed.\n+        }\n+    }\n+\n+    CentralMaster* const central;\n+    MWorkerListItem::WPtr target;\n+    int const message;\n+    uint32_t const neighborName;\n+    NeighborsInfo::NeighborWPtr neighborPtr;\n+private:\n+    SetNeighborOneShot(CentralMaster* central_,\n+                       MWorkerListItem::Ptr const& target_,\n+                       int msg_,\n+                       uint32_t neighborName_,\n+                       NeighborsInfo::NeighborPtr const& neighborPtr_) :\n+                    central(central_), target(target_), message(msg_), neighborName(neighborName_),\n+                    neighborPtr(neighborPtr_) {\n+            _oneShot = true;\n+        }\n+};\n+\n+\n+\n+util::CommandTracked::Ptr SetNeighborOneShot::createCommand() {\n+    struct SetNeighborCmd : public util::CommandTracked {\n+        SetNeighborCmd(SetNeighborOneShot::Ptr const& ptr) : oneShotData(ptr) {}\n+        void action(util::CmdData*) override {\n+            auto oSData = std::dynamic_pointer_cast<SetNeighborOneShot>(oneShotData.lock());\n+            if (oSData != nullptr) {\n+                oSData->central->setWorkerNeighbor(oSData->target, oSData->message, oSData->neighborName);\n+            }\n+        }\n+        std::weak_ptr<SetNeighborOneShot> oneShotData;\n+    };\n+    auto ptr = std::dynamic_pointer_cast<SetNeighborOneShot>(getDoListItemPtr());\n+    return std::make_shared<SetNeighborCmd>(ptr);\n+}\n+\n+\n+util::CommandTracked::Ptr MWorkerList::createCommand() {\n+    return createCommandMaster(_central);\n+}\n+\n+\n+util::CommandTracked::Ptr MWorkerList::createCommandMaster(CentralMaster* centralM) {\n+    // The master probably doesn't need to make any checks on the list.\n+    return nullptr;\n+}\n+\n+\n+// Returns pointer to new item when new worker added, otherwise nullptr.\n+MWorkerListItem::Ptr MWorkerList::addWorker(std::string const& ip, int udpPort, int tcpPort) {\n+    NetworkAddress udpAddress(ip, udpPort);\n+    NetworkAddress tcpAddress(ip, tcpPort);\n+\n+\n+    // If it is already in the map, do not change its name.\n+    std::lock_guard<std::mutex> lock(_mapMtx);\n+    auto iter = _ipMap.find(udpAddress);\n+    if (iter != _ipMap.end()) {\n+        LOGS(_log, LOG_LVL_WARN, \"addWorker, Could not add worker as worker already exists. \" <<\n+                ip << \":\" << udpPort);\n+        return nullptr;\n+    }\n+    // Get an id and make new worker item\n+    auto workerListItem = MWorkerListItem::create(_sequenceName++, udpAddress, tcpAddress, _central);\n+    _ipMap.insert(std::make_pair(udpAddress, workerListItem));\n+    _nameMap.insert(std::make_pair(workerListItem->getId(), workerListItem));\n+    LOGS(_log, LOG_LVL_INFO, \"Added worker \" << *workerListItem);\n+    _flagListChange();\n+\n+    return workerListItem;\n+}\n+\n+bool MWorkerList::sendListTo(uint64_t msgId, std::string const& ip, short port,\n+                            std::string const& ourHostName, short ourPort) {\n+    NetworkAddress address(ip, port);\n+    StringElement workerList;\n+    {\n+        std::lock_guard<std::mutex> lock(_mapMtx);\n+        if (_wListChanged || _stateListData == nullptr) {\n+            _wListChanged = false;\n+            /// At this time, all workers should easily fit in a single message.\n+            /// TODO send multiple messages (if needed) with each having the address and range of 100 workers.\n+            ///      This version is useful for testing. _stateListData becomes a vector.\n+            proto::LdrMastWorkerList protoList;\n+            protoList.set_workercount(_nameMap.size());\n+            for (auto const& item : _nameMap ) {\n+                proto::WorkerListItem* protoItem = protoList.add_worker();\n+                MWorkerListItem::Ptr const& wListItem = item.second;\n+                protoItem->set_wid(wListItem->getId());\n+            }\n+            protoList.SerializeToString(&(workerList.element));\n+            LoaderMsg workerListMsg(LoaderMsg::MAST_WORKER_LIST, msgId, ourHostName, ourPort);\n+            _stateListData = std::make_shared<BufferUdp>();\n+            workerListMsg.appendToData(*_stateListData);\n+            workerList.appendToData(*_stateListData);\n+        }\n+    }\n+\n+    _central->sendBufferTo(ip, port, *_stateListData);\n+\n+    // See if this worker is know.\n+    MWorkerListItem::Ptr workerItem;\n+    {\n+        // See if this is a worker in our map\n+        std::lock_guard<std::mutex> lock(_mapMtx);\n+        auto iter = _ipMap.find(address);\n+        if (iter != _ipMap.end()) {\n+            workerItem = iter->second;\n+        }\n+    }\n+    if (workerItem != nullptr) {\n+        workerItem->sendListToWorkerInfoReceived();\n+    }\n+    return true;\n+}\n+\n+\n+\n+\n+std::pair<std::vector<MWorkerListItem::Ptr>, std::vector<MWorkerListItem::Ptr>>\n+MWorkerList::getActiveInactiveWorkerLists() {\n+    std::vector<MWorkerListItem::Ptr> active;\n+    std::vector<MWorkerListItem::Ptr> inactive;\n+    std::lock_guard<std::mutex> lck(_mapMtx);\n+    for(auto const& elem : _nameMap) {\n+        auto item = elem.second;\n+        if (item->isActive()) {\n+            active.push_back(item);\n+        } else {\n+            inactive.push_back(item);\n+        }\n+    }\n+    auto pair = std::make_pair(active, inactive);\n+    return pair;\n+}\n+\n+\n+// must lock _mapMtx before calling this function\n+void MWorkerList::_flagListChange() {\n+    _wListChanged = true;\n+    // On the Master, flag each worker in the list that it needs to send an updated list to it's worker.\n+    for (auto const& elem : _nameMap) {\n+        auto const& item = elem.second;\n+        item->flagNeedToSendList();\n+    }\n+}\n+\n+\n+std::string MWorkerList::dump() const {\n+    std::stringstream os;\n+    os << \"MWorkerList:\\n\";\n+    {\n+        std::lock_guard<std::mutex> lck(_mapMtx);\n+        for (auto elem:_nameMap) {\n+            os << \"  \" << *elem.second << \"\\n\";\n+        }\n+        os << \"MWorkerList ip:\\n\";\n+        for (auto elem:_ipMap) {\n+            os << \"  \" << *elem.second << \"\\n\";\n+        }\n+    }\n+    return os.str();\n+}\n+\n+\n+void MWorkerListItem::addDoListItems(Central *central) {\n+    LOGS(_log, LOG_LVL_DEBUG, \"MWorkerListItem::addDoListItems\");\n+    std::lock_guard<std::mutex> lck(_doListItemsMtx);\n+    if (_sendListToWorker == nullptr) {\n+        _sendListToWorker = std::make_shared<SendListToWorker>(shared_from_this(), _central);\n+        _central->addDoListItem(_sendListToWorker);\n+    }\n+    if (_reqWorkerKeyInfo == nullptr) {\n+        _reqWorkerKeyInfo = std::make_shared<ReqWorkerKeyInfo>(shared_from_this(), _central);\n+        _central->addDoListItem(_reqWorkerKeyInfo);\n+    }\n+}\n+\n+\n+void MWorkerListItem::flagNeedToSendList() {\n+    auto slw = _sendListToWorker;\n+    if (slw != nullptr) { slw->setNeedInfo(); }\n+}\n+\n+\n+void MWorkerListItem::sendListToWorkerInfoReceived() {\n+    auto slw = _sendListToWorker;\n+    if (slw != nullptr) {\n+        // _sendListToWorker is a tough one to tell if the worker got the info, so\n+        // it is assumed that this worked when the list is sent. The worker\n+        // will either ask for it or it will be sent again later.\n+        slw->infoReceived();\n+    }\n+}\n+\n+\n+void MWorkerListItem::setRangeStr(StringRange const& strRange) {\n+    std::lock_guard<std::mutex> lck(_mtx);\n+    _range = strRange;\n+}\n+\n+\n+void MWorkerListItem::setAllInclusiveRange() {\n+    LOGS(_log, LOG_LVL_INFO, \"MWorkerListItem::setAllInclusiveRange for name=\" << _wId);\n+    std::lock_guard<std::mutex> lck(_mtx);\n+    _range.setAllInclusiveRange();\n+    _active = true;  /// First worker.\n+}\n+\n+\n+void MWorkerListItem::setNeighborsInfo(NeighborsInfo const& nInfo) {\n+    std::lock_guard<std::mutex> lck(_mtx);\n+    _neighborsInfo.keyCount = nInfo.keyCount;\n+    _neighborsInfo.recentAdds = nInfo.recentAdds;\n+\n+    auto old = _neighborsInfo.neighborLeft->get();\n+    if (old != 0) {\n+        LOGS(_log, LOG_LVL_WARN, \"Worker=\" << _wId <<\n+                \"neighborLeft changing from valid old=\" << old <<\n+                \" to new=\" << nInfo.neighborLeft->get());\n+    }\n+    if (old != nInfo.neighborLeft->get()) {\n+        LOGS(_log, LOG_LVL_INFO, \"Worker=\" << _wId <<\n+                \"neighborLeft=\" << nInfo.neighborLeft->get());\n+    }\n+    _neighborsInfo.neighborLeft->update(nInfo.neighborLeft->get());\n+\n+\n+    old = _neighborsInfo.neighborRight->get();\n+    if (old != 0) {\n+        LOGS(_log, LOG_LVL_WARN, \"Worker=\" << _wId <<\n+                \"neighborRight changing from valid old=\" << old <<\n+                \" to new=\" << nInfo.neighborRight->get());\n+    }\n+    if (old != nInfo.neighborRight->get()) {\n+\n+        LOGS(_log, LOG_LVL_INFO, \"Worker=\" << _wId <<\n+                \"neighborRight=\" << nInfo.neighborRight->get());\n+    }\n+    _neighborsInfo.neighborRight->update(nInfo.neighborRight->get());\n+}\n+\n+\n+int MWorkerListItem::getKeyCount() const {\n+    return _neighborsInfo.keyCount;\n+}\n+\n+\n+std::ostream& operator<<(std::ostream& os, MWorkerListItem const& item) {\n+    os << \"name=\" << item._wId << \" address=\" << *item._udpAddress << \" range(\" << item._range << \")\";","path":"core/modules/loader/MWorkerList.cc","position":332,"original_position":333,"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":"other places that use _range take a lock. That should be done here too I think?","created_at":"2018-11-30T21:50:17Z","updated_at":"2018-11-30T21:50:17Z","html_url":"https://github.com/lsst/qserv/pull/426#discussion_r238013028","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/238013028"},"html":{"href":"https://github.com/lsst/qserv/pull/426#discussion_r238013028"},"pull_request":{"href":"https://api.github.com/repos/lsst/qserv/pulls/426"}},"in_reply_to_id":233702008},"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-30T21:50:17Z","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"}}