)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2403376929ba6ae0470136570adb3a5968551257","unresolved":false,"context_lines":[{"line_number":14,"context_line":""},{"line_number":15,"context_line":"this patch bucketizes the fragments by etag and if it doesn\u0027t"},{"line_number":16,"context_line":"have enough continues to exhaust the node iterator (parity"},{"line_number":17,"context_line":"next) until it has a large enough matching set."},{"line_number":18,"context_line":""},{"line_number":19,"context_line":"Change-Id: Ib710a133ce1be278365067fd0d6610d80f1f7372"},{"line_number":20,"context_line":"Co-Authored-By: Clay Gerrard \u003cclay.gerrard@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"fa1b9901_076d9930","line":17,"updated":"2015-08-19 17:58:58.000000000","message":"\"parity next\"? but the iter is shuffled so the parity and data nodes could come in any order, no?\n\n#headhurts","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":14,"context_line":""},{"line_number":15,"context_line":"this patch bucketizes the fragments by etag and if it doesn\u0027t"},{"line_number":16,"context_line":"have enough continues to exhaust the node iterator (parity"},{"line_number":17,"context_line":"next) until it has a large enough matching set."},{"line_number":18,"context_line":""},{"line_number":19,"context_line":"Change-Id: Ib710a133ce1be278365067fd0d6610d80f1f7372"},{"line_number":20,"context_line":"Co-Authored-By: Clay Gerrard \u003cclay.gerrard@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"fa1b9901_80df4f0c","line":17,"in_reply_to":"fa1b9901_076d9930","updated":"2015-08-19 19:07:56.000000000","message":"yeah my head hurts too... I suppose this is true only on a PUT but on a seubsequent GET (or any other new node_iter) who knows what the first 10 will be","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"}],"swift/common/utils.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":2273,"context_line":"        try:"},{"line_number":2274,"context_line":"            self._responses.put(func(*args, **kwargs))"},{"line_number":2275,"context_line":"        finally:"},{"line_number":2276,"context_line":"            self._inflight -\u003d 1"},{"line_number":2277,"context_line":""},{"line_number":2278,"context_line":"    def spawn(self, func, *args, **kwargs):"},{"line_number":2279,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_5f94ae41","side":"PARENT","line":2276,"updated":"2015-08-25 21:27:11.000000000","message":"_inflight was being decremented to early for what I needed.\n\nInside the `for resp in pile` loop I needed to know how many spawned guys were \"un-consumed\" more so than how many hadn\u0027t \"finished\" (since it\u0027s possible two would finish in the same hub cycle before the proxy code got back in control and if it was only counting \"inflight\" it\u0027d think there was one fewer \"outstanding\" responses than it actually has queued up and ready to classify.","commit_id":"e354f897c3f5b40606f05a14108d90bc83be71aa"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"379026b6586492ebc945fb0a2e84aa2074f5b493","unresolved":false,"context_lines":[{"line_number":2312,"context_line":"            else:"},{"line_number":2313,"context_line":"                return self._responses.get()"},{"line_number":2314,"context_line":"        finally:"},{"line_number":2315,"context_line":"            self._pending -\u003d 1"},{"line_number":2316,"context_line":""},{"line_number":2317,"context_line":""},{"line_number":2318,"context_line":"class ModifiedParseResult(ParseResult):"}],"source_content_type":"text/x-python","patch_set":10,"id":"da20952f_0e30d755","line":2315,"updated":"2015-08-26 19:39:47.000000000","message":"_pending will go increasingly negative whenever next() is called on an empty pile. Should bottom out at zero so when a new job is spawned it increments to +1.","commit_id":"fd9449358a8b20882d86a8afb2ab24d3e64fa5cd"}],"swift/proxy/controllers/base.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":1180,"context_line":"        that is (request_node_count - num_primaries), when handoffs goes"},{"line_number":1181,"context_line":"        higher than that it means one of the primaries must have been"},{"line_number":1182,"context_line":"        skipped because of error limiting before we consumed all of our"},{"line_number":1183,"context_line":"        nodes_left."},{"line_number":1184,"context_line":"        \"\"\""},{"line_number":1185,"context_line":"        if not self.app.log_handoffs:"},{"line_number":1186,"context_line":"            return"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_ed295b9c","line":1183,"updated":"2015-08-25 21:27:11.000000000","message":"I had to read up on the behavior here, go read up on If1b77c18c880b096e8ab1df3008db40ce313835d or ask @sam","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"}],"swift/proxy/controllers/obj.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ef4e52c2574b2cb389284707e769f2aa2cb37009","unresolved":false,"context_lines":[{"line_number":1974,"context_line":"                            append((get, parts_iter))"},{"line_number":1975,"context_line":"                        if len(etag_buckets[etag]) \u003e len(max_bucket):"},{"line_number":1976,"context_line":"                            max_bucket \u003d etag_buckets[etag]"},{"line_number":1977,"context_line":"                        if len(good_gets) \u003d\u003d num_gets:"},{"line_number":1978,"context_line":"                            # we\u0027ve got enough good responses but we need to"},{"line_number":1979,"context_line":"                            # make sure they are from the same etag set and"},{"line_number":1980,"context_line":"                            # if not spawn more getters to the parity nodes"}],"source_content_type":"text/x-python","patch_set":4,"id":"fa1b9901_d76ca80a","line":1977,"updated":"2015-08-17 17:56:38.000000000","message":"does this not need to be \u003e\u003d condition?\n\ne.g. fire off one extra get at line 1983, which comes back good but not in max_bucket, we\u0027d want to fire off another but now len(good_gets) \u003e num_gets.","commit_id":"c48fe151f83c2719663d38443eab20e903a3410e"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"41e74e62bd1cea9184add743802de021967d8f58","unresolved":false,"context_lines":[{"line_number":1974,"context_line":"                            append((get, parts_iter))"},{"line_number":1975,"context_line":"                        if len(etag_buckets[etag]) \u003e len(max_bucket):"},{"line_number":1976,"context_line":"                            max_bucket \u003d etag_buckets[etag]"},{"line_number":1977,"context_line":"                        if len(good_gets) \u003d\u003d num_gets:"},{"line_number":1978,"context_line":"                            # we\u0027ve got enough good responses but we need to"},{"line_number":1979,"context_line":"                            # make sure they are from the same etag set and"},{"line_number":1980,"context_line":"                            # if not spawn more getters to the parity nodes"}],"source_content_type":"text/x-python","patch_set":4,"id":"fa1b9901_3064c747","line":1977,"in_reply_to":"fa1b9901_d76ca80a","updated":"2015-08-18 23:39:32.000000000","message":"yup, had I written a test like I should have I would have caught that, thanks!","commit_id":"c48fe151f83c2719663d38443eab20e903a3410e"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ef4e52c2574b2cb389284707e769f2aa2cb37009","unresolved":false,"context_lines":[{"line_number":1979,"context_line":"                            # make sure they are from the same etag set and"},{"line_number":1980,"context_line":"                            # if not spawn more getters to the parity nodes"},{"line_number":1981,"context_line":"                            # to get some more"},{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c num_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"}],"source_content_type":"text/x-python","patch_set":4,"id":"fa1b9901_e208082b","line":1982,"updated":"2015-08-17 17:56:38.000000000","message":"what stops this looping for ever if we never get back another good get (so len(good_gets) \u003d\u003d num_gets remains true) ? Feels like there needs to be a break when total spawned gets \u003d\u003d number of nodes.","commit_id":"c48fe151f83c2719663d38443eab20e903a3410e"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"41e74e62bd1cea9184add743802de021967d8f58","unresolved":false,"context_lines":[{"line_number":1979,"context_line":"                            # make sure they are from the same etag set and"},{"line_number":1980,"context_line":"                            # if not spawn more getters to the parity nodes"},{"line_number":1981,"context_line":"                            # to get some more"},{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c num_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"}],"source_content_type":"text/x-python","patch_set":4,"id":"fa1b9901_30cb6720","line":1982,"in_reply_to":"fa1b9901_e208082b","updated":"2015-08-18 23:39:32.000000000","message":"I added a note explaining how it exists, in the case you mention the node_iter will eventually run out and we\u0027ll stop incrementing the for loop counter \u0027gets\u0027 so then we\u0027ll drop out.  This case is also covered in the new test I added","commit_id":"c48fe151f83c2719663d38443eab20e903a3410e"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":1960,"context_line":"                               req, node_iter, partition,"},{"line_number":1961,"context_line":"                               policy)"},{"line_number":1962,"context_line":""},{"line_number":1963,"context_line":"                gets \u003d list(pile)"},{"line_number":1964,"context_line":"                good_gets \u003d []"},{"line_number":1965,"context_line":"                bad_gets \u003d []"},{"line_number":1966,"context_line":"                for get, parts_iter in gets:"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_207c431e","side":"PARENT","line":1963,"updated":"2015-08-19 21:03:24.000000000","message":"whoa, wait a minute - doesn\u0027t this consume all the connections - why is there here?","commit_id":"446a2b3606c11e62fb7dcf689ed17a97fe268428"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1981,"context_line":"                            # to get some more"},{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c min_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"},{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_79c1a353","line":1984,"updated":"2015-08-19 17:43:02.000000000","message":"this works, but is slow - each \u0027extra\u0027 request that may be needed is issued sequentially. Seems like it ought to be possible to know that min_gets - len(max_bucket) are needed and spawn them in parallel, test the outcome, and repeat if necessary.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1981,"context_line":"                            # to get some more"},{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c min_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"},{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_c04787ba","line":1984,"in_reply_to":"fa1b9901_79c1a353","updated":"2015-08-19 19:07:56.000000000","message":"well, I had that in the last patch (something very similar) and it came out too complex.  This trades off some perf I guess in a non-happy-path case for simplicity.  The simplicity is gained from not having an outter loop (and less control paths/conditions to test)","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c min_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"},{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"},{"line_number":1988,"context_line":"                        # we stop spawning above because node_iter runs out,"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_01411aa9","line":1985,"updated":"2015-08-19 17:43:02.000000000","message":"modification of a list that is being iterated always scares me, but this is OK I think because it is appending to the list not inserting.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c min_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"},{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"},{"line_number":1988,"context_line":"                        # we stop spawning above because node_iter runs out,"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_80142fa0","line":1985,"in_reply_to":"fa1b9901_01411aa9","updated":"2015-08-19 19:07:56.000000000","message":"Done","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":1982,"context_line":"                            if len(max_bucket) \u003c min_gets:"},{"line_number":1983,"context_line":"                                pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                           node_iter, partition, policy)"},{"line_number":1985,"context_line":"                                gets +\u003d list(pile)"},{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"},{"line_number":1988,"context_line":"                        # we stop spawning above because node_iter runs out,"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_a096133d","line":1985,"in_reply_to":"fa1b9901_01411aa9","updated":"2015-08-19 21:03:24.000000000","message":"no, we shouldn\u0027t have to do this... spawning\u0027 in the pile should have been enough - this is crazy","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"},{"line_number":1988,"context_line":"                        # we stop spawning above because node_iter runs out,"},{"line_number":1989,"context_line":"                        # and then \u0027gets\u0027 runs out..."},{"line_number":1990,"context_line":"                    else:"},{"line_number":1991,"context_line":"                        bad_gets.append((get, parts_iter))"},{"line_number":1992,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_e11666f0","line":1989,"updated":"2015-08-19 17:43:02.000000000","message":"oh right, i get it. when node_iter is exhausted then get.last_status will be None so we append to bad_gets and stop appending to gets, so loop exits.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"},{"line_number":1988,"context_line":"                        # we stop spawning above because node_iter runs out,"},{"line_number":1989,"context_line":"                        # and then \u0027gets\u0027 runs out..."},{"line_number":1990,"context_line":"                    else:"},{"line_number":1991,"context_line":"                        bad_gets.append((get, parts_iter))"},{"line_number":1992,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_a01933d9","line":1989,"in_reply_to":"fa1b9901_e11666f0","updated":"2015-08-19 19:07:56.000000000","message":"Done","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":1986,"context_line":"                        # NOTE:  it may not be obvious but we eventually"},{"line_number":1987,"context_line":"                        # exit this loop (if not big enough buckets) when"},{"line_number":1988,"context_line":"                        # we stop spawning above because node_iter runs out,"},{"line_number":1989,"context_line":"                        # and then \u0027gets\u0027 runs out..."},{"line_number":1990,"context_line":"                    else:"},{"line_number":1991,"context_line":"                        bad_gets.append((get, parts_iter))"},{"line_number":1992,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_00cd7f65","line":1989,"in_reply_to":"fa1b9901_e11666f0","updated":"2015-08-19 21:03:24.000000000","message":"The 2 x replica_count stuff is enforced *in* the node_iter.  If the node generator was a class and exposed it\u0027s node_iter.nodes_left as a property you could check that before spawning and this comment wouldn\u0027t be needed.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":1991,"context_line":"                        bad_gets.append((get, parts_iter))"},{"line_number":1992,"context_line":""},{"line_number":1993,"context_line":"            req.range \u003d orig_range"},{"line_number":1994,"context_line":"            if len(max_bucket) \u003d\u003d min_gets:"},{"line_number":1995,"context_line":"                # headers can come from any of the getters"},{"line_number":1996,"context_line":"                resp_headers \u003d HeaderKeyDict("},{"line_number":1997,"context_line":"                    max_bucket[0][0].source_headers[-1])"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_40e5b7d9","line":1994,"updated":"2015-08-19 21:03:24.000000000","message":"I don\u0027t see why this wouldn\u0027t be \u003e\u003d","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":1962,"context_line":"                bad_gets \u003d []"},{"line_number":1963,"context_line":"                etag_buckets \u003d collections.defaultdict(list)"},{"line_number":1964,"context_line":"                best_etag \u003d None"},{"line_number":1965,"context_line":"                for gets_done, (get, parts_iter) in enumerate(pile, 1):"},{"line_number":1966,"context_line":"                    if is_success(get.last_status):"},{"line_number":1967,"context_line":"                        etag \u003d HeaderKeyDict("},{"line_number":1968,"context_line":"                            get.last_headers)[\u0027X-Object-Sysmeta-Ec-Etag\u0027]"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_6da1cb45","line":1965,"updated":"2015-08-25 21:27:11.000000000","message":"gets_done is no longer used","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ce22bec9ab6e970b79f1369cb9c7b022d6740b2e","unresolved":false,"context_lines":[{"line_number":1962,"context_line":"                bad_gets \u003d []"},{"line_number":1963,"context_line":"                etag_buckets \u003d collections.defaultdict(list)"},{"line_number":1964,"context_line":"                best_etag \u003d None"},{"line_number":1965,"context_line":"                for gets_done, (get, parts_iter) in enumerate(pile, 1):"},{"line_number":1966,"context_line":"                    if is_success(get.last_status):"},{"line_number":1967,"context_line":"                        etag \u003d HeaderKeyDict("},{"line_number":1968,"context_line":"                            get.last_headers)[\u0027X-Object-Sysmeta-Ec-Etag\u0027]"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_d87f74de","line":1965,"in_reply_to":"fa1b9901_6da1cb45","updated":"2015-08-26 03:03:17.000000000","message":"Done","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":1979,"context_line":"                            pile._pending) and node_iter.nodes_left \u003e 0:"},{"line_number":1980,"context_line":"                        # we need more matching responses to reach ec_ndata"},{"line_number":1981,"context_line":"                        # than we have pending gets, as long as we still have"},{"line_number":1982,"context_line":"                        # nodes in node_iter we can spawn another"},{"line_number":1983,"context_line":"                        pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                   safe_iter, partition, policy)"},{"line_number":1985,"context_line":""}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_0d792fc8","line":1982,"updated":"2015-08-25 21:27:11.000000000","message":"I\u0027d be happy for different words here; the point is - we need ec_ndata nodes to agree on *something* and between the best we\u0027ve got less the number of requests still have outstanding we\u0027re not gunna make it - so if we have nodes_left - let\u0027s get \u0027em going!","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"379026b6586492ebc945fb0a2e84aa2074f5b493","unresolved":false,"context_lines":[{"line_number":1974,"context_line":"                    else:"},{"line_number":1975,"context_line":"                        bad_gets.append((get, parts_iter))"},{"line_number":1976,"context_line":"                    matching_response_count \u003d max("},{"line_number":1977,"context_line":"                        len(etag_buckets[best_etag]), len(bad_gets))"},{"line_number":1978,"context_line":"                    if (policy.ec_ndata - matching_response_count \u003e"},{"line_number":1979,"context_line":"                            pile._pending) and node_iter.nodes_left \u003e 0:"},{"line_number":1980,"context_line":"                        # we need more matching responses to reach ec_ndata"}],"source_content_type":"text/x-python","patch_set":10,"id":"da20952f_b3aa7884","line":1977,"updated":"2015-08-26 19:39:47.000000000","message":"so, if bad gets (i.e. 416s) reach ec_ndata before the good gets then we stop looking. There *may* be ec_ndata 2xx out there on handoffs, but I can live with \"we may a reasonable effort\".\n\nWe could come up with a heuristic to account for when good gets are *nearly* as many as bad gets, so keep going ... but someone can come back and iterate on that. IMHO this is good enough for now.","commit_id":"fd9449358a8b20882d86a8afb2ab24d3e64fa5cd"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"379026b6586492ebc945fb0a2e84aa2074f5b493","unresolved":false,"context_lines":[{"line_number":1981,"context_line":"                        # than we have pending gets, as long as we still have"},{"line_number":1982,"context_line":"                        # nodes in node_iter we can spawn another"},{"line_number":1983,"context_line":"                        pile.spawn(self._fragment_GET_request, req,"},{"line_number":1984,"context_line":"                                   safe_iter, partition, policy)"},{"line_number":1985,"context_line":""},{"line_number":1986,"context_line":"            req.range \u003d orig_range"},{"line_number":1987,"context_line":"            if len(etag_buckets[best_etag]) \u003e\u003d policy.ec_ndata:"}],"source_content_type":"text/x-python","patch_set":10,"id":"da20952f_ce241fb1","line":1984,"updated":"2015-08-26 19:39:47.000000000","message":"worked example, for my benefit at least, assuming ec_ndata\u003d10:\n\n  iteration  |    pending   |    good gets   |    bad gets |    spawn\n        1             9              0               1            no\n        2             8              0               2            no\n        3             7              1               2           yes\n        4             7              2               2           yes\n        5             7              3               2            no\n        6             6              3               3           yes\n        7             6              3               4            no\n        8             5              4               4           yes\n        9             5              5               4            no\n       10             4              6               4            no\n       11             3              7               4            no\n       12             2              8               4            no\n       13             1              9               4            no\n       14             0             10               4            no\n\n14 requests, 10 good, 4 bad.","commit_id":"fd9449358a8b20882d86a8afb2ab24d3e64fa5cd"}],"test/probe/common.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":283,"context_line":"    def read(self, amount):"},{"line_number":284,"context_line":"        if len(self.buff) \u003c amount:"},{"line_number":285,"context_line":"            try:"},{"line_number":286,"context_line":"                self.buff \u003d next(self)"},{"line_number":287,"context_line":"            except StopIteration:"},{"line_number":288,"context_line":"                pass"},{"line_number":289,"context_line":"        rv, self.buff \u003d self.buff[:amount], self.buff[amount:]"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_851a017c","line":286,"updated":"2015-08-19 21:03:24.000000000","message":"I think this should be self.buff +\u003d next(self)","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"}],"test/probe/test_object_handoff.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":245,"context_line":"            self.account, container_name, object_name)"},{"line_number":246,"context_line":""},{"line_number":247,"context_line":"        # shutdown one of the primary data nodes"},{"line_number":248,"context_line":"        failed_primary \u003d random.choice(onodes[:self.policy.ec_ndata])"},{"line_number":249,"context_line":"        failed_primary_device_path \u003d self.device_dir(\u0027object\u0027, failed_primary)"},{"line_number":250,"context_line":"        self.kill_drive(failed_primary_device_path)"},{"line_number":251,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_9ffa5742","line":248,"updated":"2015-08-19 17:43:02.000000000","message":"why restrict to the first ec_ndata?","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":245,"context_line":"            self.account, container_name, object_name)"},{"line_number":246,"context_line":""},{"line_number":247,"context_line":"        # shutdown one of the primary data nodes"},{"line_number":248,"context_line":"        failed_primary \u003d random.choice(onodes[:self.policy.ec_ndata])"},{"line_number":249,"context_line":"        failed_primary_device_path \u003d self.device_dir(\u0027object\u0027, failed_primary)"},{"line_number":250,"context_line":"        self.kill_drive(failed_primary_device_path)"},{"line_number":251,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_a08053c1","line":248,"in_reply_to":"fa1b9901_9ffa5742","updated":"2015-08-19 19:07:56.000000000","message":"yeah, guess it doesn\u0027t matter a ton","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":252,"context_line":"        # overwrite our object with some new data"},{"line_number":253,"context_line":"        new_contents \u003d Body()"},{"line_number":254,"context_line":"        client.put_object(self.url, self.token, container_name,"},{"line_number":255,"context_line":"                          object_name, contents\u003dnew_contents)"},{"line_number":256,"context_line":""},{"line_number":257,"context_line":"        # restore failed primary device"},{"line_number":258,"context_line":"        self.revive_drive(failed_primary_device_path)"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_c4bfa2a8","line":255,"updated":"2015-08-19 17:43:02.000000000","message":"nit \u0026 totally paranoid: add assertion that old_contents.etag !\u003d new_contents.etag just in case someone goes and modifies the Body class to always generate the same content.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":252,"context_line":"        # overwrite our object with some new data"},{"line_number":253,"context_line":"        new_contents \u003d Body()"},{"line_number":254,"context_line":"        client.put_object(self.url, self.token, container_name,"},{"line_number":255,"context_line":"                          object_name, contents\u003dnew_contents)"},{"line_number":256,"context_line":""},{"line_number":257,"context_line":"        # restore failed primary device"},{"line_number":258,"context_line":"        self.revive_drive(failed_primary_device_path)"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_6086bbdc","line":255,"in_reply_to":"fa1b9901_c4bfa2a8","updated":"2015-08-19 19:07:56.000000000","message":"will fix","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":271,"context_line":"        # read object"},{"line_number":272,"context_line":"        for i in range(10):"},{"line_number":273,"context_line":"            # do it a few times to make sure node shuffle doesn\u0027t"},{"line_number":274,"context_line":"            # accidently work around the issue for us"},{"line_number":275,"context_line":"            resp_etag \u003d self.get_object(container_name, object_name)"},{"line_number":276,"context_line":"            self.assertEqual(resp_etag, new_contents.etag)"},{"line_number":277,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_249d3e0a","line":274,"updated":"2015-08-19 17:43:02.000000000","message":"ok but how about killing some of the other primaries to force the GET to pass over the bad etag?\n\ne.g. http://paste.openstack.org/show/422052/","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":271,"context_line":"        # read object"},{"line_number":272,"context_line":"        for i in range(10):"},{"line_number":273,"context_line":"            # do it a few times to make sure node shuffle doesn\u0027t"},{"line_number":274,"context_line":"            # accidently work around the issue for us"},{"line_number":275,"context_line":"            resp_etag \u003d self.get_object(container_name, object_name)"},{"line_number":276,"context_line":"            self.assertEqual(resp_etag, new_contents.etag)"},{"line_number":277,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_8062ef3c","line":274,"in_reply_to":"fa1b9901_249d3e0a","updated":"2015-08-19 19:07:56.000000000","message":"I\u0027ll take a gander, thanks!","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":271,"context_line":"        # read object"},{"line_number":272,"context_line":"        for i in range(10):"},{"line_number":273,"context_line":"            # do it a few times to make sure node shuffle doesn\u0027t"},{"line_number":274,"context_line":"            # accidently work around the issue for us"},{"line_number":275,"context_line":"            resp_etag \u003d self.get_object(container_name, object_name)"},{"line_number":276,"context_line":"            self.assertEqual(resp_etag, new_contents.etag)"},{"line_number":277,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_40a37700","line":274,"in_reply_to":"fa1b9901_249d3e0a","updated":"2015-08-19 21:03:24.000000000","message":"nice.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"096cf623645a34642267ca3d9affabebf105cb83","unresolved":false,"context_lines":[{"line_number":274,"context_line":"        while len(failed_nodes) \u003c 3:"},{"line_number":275,"context_line":"            node \u003d random.choice(onodes)"},{"line_number":276,"context_line":"            if node in failed_nodes:"},{"line_number":277,"context_line":"                continue"},{"line_number":278,"context_line":"            failed_nodes.append(node)"},{"line_number":279,"context_line":"            failed_primary_device_path \u003d self.device_dir(\u0027object\u0027, node)"},{"line_number":280,"context_line":"            self.kill_drive(failed_primary_device_path)"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_540c7aec","line":277,"updated":"2015-08-20 17:17:14.000000000","message":"yup thats better than what i suggested","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":283,"context_line":"        Manager([\u0027proxy\u0027]).restart()"},{"line_number":284,"context_line":""},{"line_number":285,"context_line":"        resp_etag \u003d self.get_object(container_name, object_name)"},{"line_number":286,"context_line":"        self.assertEqual(resp_etag, new_contents.etag)"},{"line_number":287,"context_line":""},{"line_number":288,"context_line":"if __name__ \u003d\u003d \u0027__main__\u0027:"},{"line_number":289,"context_line":"    main()"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_df051e11","line":286,"updated":"2015-08-25 21:27:11.000000000","message":"I haven\u0027t really reviewed this test, but @acoles \u0026 @peluse like it - I think I wrote some of it :\\","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ce22bec9ab6e970b79f1369cb9c7b022d6740b2e","unresolved":false,"context_lines":[{"line_number":283,"context_line":"        Manager([\u0027proxy\u0027]).restart()"},{"line_number":284,"context_line":""},{"line_number":285,"context_line":"        resp_etag \u003d self.get_object(container_name, object_name)"},{"line_number":286,"context_line":"        self.assertEqual(resp_etag, new_contents.etag)"},{"line_number":287,"context_line":""},{"line_number":288,"context_line":"if __name__ \u003d\u003d \u0027__main__\u0027:"},{"line_number":289,"context_line":"    main()"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_893d292d","line":286,"in_reply_to":"fa1b9901_df051e11","updated":"2015-08-26 03:03:17.000000000","message":"Done","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"}],"test/probe/test_reconstructor_revert.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":54,"context_line":"        return self.chunk"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":"    def __next__(self):"},{"line_number":57,"context_line":"        return next(self)"},{"line_number":58,"context_line":""},{"line_number":59,"context_line":""},{"line_number":60,"context_line":"class TestReconstructorRevert(ECProbeTest):"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_9f2246a2","side":"PARENT","line":57,"updated":"2015-08-25 21:27:11.000000000","message":"this was just moved to probe.common","commit_id":"e354f897c3f5b40606f05a14108d90bc83be71aa"}],"test/unit/proxy/controllers/test_obj.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":1372,"context_line":"        # c2 get"},{"line_number":1373,"context_line":"        codes \u003d [404, 200] * self.policy.ec_ndata"},{"line_number":1374,"context_line":"        headers \u003d {"},{"line_number":1375,"context_line":"            \u0027X-Object-Sysmeta-Ec-Content-Length\u0027: 0"},{"line_number":1376,"context_line":"        }"},{"line_number":1377,"context_line":"        # c1 put"},{"line_number":1378,"context_line":"        codes +\u003d [201] * self.replicas(POLICIES[1])"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_c0b78738","line":1375,"updated":"2015-08-19 21:03:24.000000000","message":"unrelated change","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1420,"context_line":"        test_data \u003d (\u0027test\u0027 * segment_size)[:-657]"},{"line_number":1421,"context_line":"        etag \u003d md5(test_data).hexdigest()"},{"line_number":1422,"context_line":"        ec_archive_bodies \u003d self._make_ec_archive_bodies(test_data)"},{"line_number":1423,"context_line":"        part \u003d self.policy.object_ring.get_part(\u0027a\u0027, \u0027c\u0027, \u0027o\u0027)"},{"line_number":1424,"context_line":"        primary_nodes \u003d self.policy.object_ring.get_part_nodes(part)"},{"line_number":1425,"context_line":"        num_ho_nodes \u003d len(list(self.policy.object_ring.get_more_nodes(part)))"},{"line_number":1426,"context_line":"        max_num_nodes_to_search \u003d len(primary_nodes) + num_ho_nodes"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_76bd3b67","line":1423,"updated":"2015-08-19 17:43:02.000000000","message":"nit: could use self.obj_ring throughout","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1420,"context_line":"        test_data \u003d (\u0027test\u0027 * segment_size)[:-657]"},{"line_number":1421,"context_line":"        etag \u003d md5(test_data).hexdigest()"},{"line_number":1422,"context_line":"        ec_archive_bodies \u003d self._make_ec_archive_bodies(test_data)"},{"line_number":1423,"context_line":"        part \u003d self.policy.object_ring.get_part(\u0027a\u0027, \u0027c\u0027, \u0027o\u0027)"},{"line_number":1424,"context_line":"        primary_nodes \u003d self.policy.object_ring.get_part_nodes(part)"},{"line_number":1425,"context_line":"        num_ho_nodes \u003d len(list(self.policy.object_ring.get_more_nodes(part)))"},{"line_number":1426,"context_line":"        max_num_nodes_to_search \u003d len(primary_nodes) + num_ho_nodes"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_605d5b78","line":1423,"in_reply_to":"fa1b9901_76bd3b67","updated":"2015-08-19 19:07:56.000000000","message":"will fix","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1424,"context_line":"        primary_nodes \u003d self.policy.object_ring.get_part_nodes(part)"},{"line_number":1425,"context_line":"        num_ho_nodes \u003d len(list(self.policy.object_ring.get_more_nodes(part)))"},{"line_number":1426,"context_line":"        max_num_nodes_to_search \u003d len(primary_nodes) + num_ho_nodes"},{"line_number":1427,"context_line":"        global ho_etag"},{"line_number":1428,"context_line":"        ho_etag \u003d 0"},{"line_number":1429,"context_line":""},{"line_number":1430,"context_line":"        def run_scenario(count):"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_d94d4f3f","line":1427,"updated":"2015-08-19 17:43:02.000000000","message":"can avoid the use of globals by holding the vars inside lists like this:\n\nhttp://paste.openstack.org/show/421973/","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1424,"context_line":"        primary_nodes \u003d self.policy.object_ring.get_part_nodes(part)"},{"line_number":1425,"context_line":"        num_ho_nodes \u003d len(list(self.policy.object_ring.get_more_nodes(part)))"},{"line_number":1426,"context_line":"        max_num_nodes_to_search \u003d len(primary_nodes) + num_ho_nodes"},{"line_number":1427,"context_line":"        global ho_etag"},{"line_number":1428,"context_line":"        ho_etag \u003d 0"},{"line_number":1429,"context_line":""},{"line_number":1430,"context_line":"        def run_scenario(count):"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_e0e08b98","line":1427,"in_reply_to":"fa1b9901_d94d4f3f","updated":"2015-08-19 19:07:56.000000000","message":"OK great!  I\u0027ll take a look as I hated using globals - internal funcs like this that need state scoped at the higher level func level has always bugged me (wrt good way to do it).  thanks","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2f439ca489c909140c7fb95cb7a1a200d036d358","unresolved":false,"context_lines":[{"line_number":1424,"context_line":"        primary_nodes \u003d self.policy.object_ring.get_part_nodes(part)"},{"line_number":1425,"context_line":"        num_ho_nodes \u003d len(list(self.policy.object_ring.get_more_nodes(part)))"},{"line_number":1426,"context_line":"        max_num_nodes_to_search \u003d len(primary_nodes) + num_ho_nodes"},{"line_number":1427,"context_line":"        global ho_etag"},{"line_number":1428,"context_line":"        ho_etag \u003d 0"},{"line_number":1429,"context_line":""},{"line_number":1430,"context_line":"        def run_scenario(count):"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_c0bc6708","line":1427,"in_reply_to":"fa1b9901_d94d4f3f","updated":"2015-08-19 21:03:24.000000000","message":"yeah, that\u0027s a good trick, you\u0027ll see it other places in swift tests - better than global probably since the global will shadow weird NameErrors if another test re-uses that name - all the normal reasons you avoid globals - python3 has nonlocal that gets around the mutable local hack","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1482,"context_line":"        for count in range(1, 6):"},{"line_number":1483,"context_line":"            run_scenario(count)"},{"line_number":1484,"context_line":""},{"line_number":1485,"context_line":"    def test_GET_with_misplaced_frags(self):"},{"line_number":1486,"context_line":"        segment_size \u003d self.policy.ec_segment_size"},{"line_number":1487,"context_line":"        test_data \u003d (\u0027test\u0027 * segment_size)[:-657]"},{"line_number":1488,"context_line":"        etag \u003d md5(test_data).hexdigest()"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_36cb43a7","line":1485,"updated":"2015-08-19 17:43:02.000000000","message":"this test is cool but doesn\u0027t relate to the change in obj.py. Like, it passes even if I revert obj.py to master.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1482,"context_line":"        for count in range(1, 6):"},{"line_number":1483,"context_line":"            run_scenario(count)"},{"line_number":1484,"context_line":""},{"line_number":1485,"context_line":"    def test_GET_with_misplaced_frags(self):"},{"line_number":1486,"context_line":"        segment_size \u003d self.policy.ec_segment_size"},{"line_number":1487,"context_line":"        test_data \u003d (\u0027test\u0027 * segment_size)[:-657]"},{"line_number":1488,"context_line":"        etag \u003d md5(test_data).hexdigest()"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_a06a93ec","line":1485,"in_reply_to":"fa1b9901_36cb43a7","updated":"2015-08-19 19:07:56.000000000","message":"yeah, it came from splitting off from another patch.  I\u0027ll take another look and see where there\u0027s a whole elsewhere, this is a routine that I happily borrowed from clayg during the patch shuffling timeframe","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1490,"context_line":""},{"line_number":1491,"context_line":"        req \u003d swob.Request.blank(\u0027/v1/a/c/o\u0027)"},{"line_number":1492,"context_line":""},{"line_number":1493,"context_line":"        primary_nodes \u003d self.policy.object_ring.get_part_nodes(1)"},{"line_number":1494,"context_line":""},{"line_number":1495,"context_line":"        response_map \u003d {"},{"line_number":1496,"context_line":"            (n[\u0027ip\u0027], n[\u0027port\u0027]): [StubResponse(200, ec_archive_bodies[i], {"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_f6cb8b51","line":1493,"updated":"2015-08-19 17:43:02.000000000","message":"nit: self.obj_ring could be used\n\nnote to self: this works get_part_nodes(1) because FakeRing returns same set of nodes for all partitions.","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1500,"context_line":"            })] for i, n in enumerate(primary_nodes)"},{"line_number":1501,"context_line":"        }"},{"line_number":1502,"context_line":""},{"line_number":1503,"context_line":"        # swap a handoff response into a data node"},{"line_number":1504,"context_line":"        node_key \u003d lambda n: (n[\u0027ip\u0027], n[\u0027port\u0027])"},{"line_number":1505,"context_line":"        data_node \u003d random.choice(primary_nodes[:self.policy.ec_ndata])"},{"line_number":1506,"context_line":"        handoff_node \u003d random.choice(primary_nodes[self.policy.ec_ndata:])"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_f642ab3d","line":1503,"updated":"2015-08-19 17:43:02.000000000","message":"the other nodes are parity not handoff?\n\nSo Paul explained to me in irc how there\u0027s not much sense in calling these parity vs data because the GET node_iter does its shuffle thing on the primary nodes. But they\u0027re definitely not handoffs??\n\nSo here we create 14 frags, map them to primary nodes, then swap two of the primary nodes\u0027 frags.\n\nThen the GET shuffles primaries and takes first 10. So what I am missing is what extra condition is the test re-creating? The swap in the test could just as well have been due to the shuffle in the node_iter? Or is it just checking that there is not some (unwanted) binding between an archive index and the node index??","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1500,"context_line":"            })] for i, n in enumerate(primary_nodes)"},{"line_number":1501,"context_line":"        }"},{"line_number":1502,"context_line":""},{"line_number":1503,"context_line":"        # swap a handoff response into a data node"},{"line_number":1504,"context_line":"        node_key \u003d lambda n: (n[\u0027ip\u0027], n[\u0027port\u0027])"},{"line_number":1505,"context_line":"        data_node \u003d random.choice(primary_nodes[:self.policy.ec_ndata])"},{"line_number":1506,"context_line":"        handoff_node \u003d random.choice(primary_nodes[self.policy.ec_ndata:])"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_7bebd013","line":1503,"in_reply_to":"fa1b9901_f642ab3d","updated":"2015-08-19 19:07:56.000000000","message":"yeah, with a better understanding here of who does what let me take another look at this too","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"370df752c9c786c71035b7b1d07a83e611e81814","unresolved":false,"context_lines":[{"line_number":1524,"context_line":"        self.assertEqual(len(leftover_responses), len(primary_nodes) -"},{"line_number":1525,"context_line":"                         self.policy.ec_ndata)"},{"line_number":1526,"context_line":""},{"line_number":1527,"context_line":"    def test_ec_get_disk_layout(self):"},{"line_number":1528,"context_line":"        def make_ec_object_stubs(x):"},{"line_number":1529,"context_line":"            segment_size \u003d self.policy.ec_segment_size"},{"line_number":1530,"context_line":"            test_data \u003d (\u0027test\u0027 * segment_size)[:-x]"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_193987f2","line":1527,"updated":"2015-08-19 17:43:02.000000000","message":"this seems to do something very similar to test_GET_with_mismatch_etag_ok() but for just one scenario and with differetn assertions (here we check that the num of frags per etag is always \u003c\u003d n_data) - feels like the same assertion could be made in test_GET_with_mismatch_etag_ok() and this test would be redundant.\n\nBut as always I have probably missed the subtlety :)","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":7479,"name":"paul luse","email":"paul.e.luse@intel.com","username":"peluse"},"change_message_id":"33b932e68d883f239491831f92adb298f15449f3","unresolved":false,"context_lines":[{"line_number":1524,"context_line":"        self.assertEqual(len(leftover_responses), len(primary_nodes) -"},{"line_number":1525,"context_line":"                         self.policy.ec_ndata)"},{"line_number":1526,"context_line":""},{"line_number":1527,"context_line":"    def test_ec_get_disk_layout(self):"},{"line_number":1528,"context_line":"        def make_ec_object_stubs(x):"},{"line_number":1529,"context_line":"            segment_size \u003d self.policy.ec_segment_size"},{"line_number":1530,"context_line":"            test_data \u003d (\u0027test\u0027 * segment_size)[:-x]"}],"source_content_type":"text/x-python","patch_set":7,"id":"fa1b9901_dbb9bc16","line":1527,"in_reply_to":"fa1b9901_193987f2","updated":"2015-08-19 19:07:56.000000000","message":"good call, clayg wrote this one and I wrote the other one at different points in time :)  Will have another look and update accordingly","commit_id":"3d8e472fed7fe7b99e4e672ecd41cf8527037684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"a0a99510fadb03520b47927e8e29c4626dee3e5a","unresolved":false,"context_lines":[{"line_number":1434,"context_line":"                  {\u0027X-Object-Sysmeta-Ec-Content-Length\u0027: len(test_data),"},{"line_number":1435,"context_line":"                   \u0027X-Object-Sysmeta-Ec-Etag\u0027: etag,"},{"line_number":1436,"context_line":"                   \u0027X-Object-Sysmeta-Ec-Frag-Index\u0027: i,"},{"line_number":1437,"context_line":"                   })] for i, n in enumerate(primary_nodes)}"},{"line_number":1438,"context_line":""},{"line_number":1439,"context_line":"            bad_etags \u003d [\u00271234\u0027] * count"},{"line_number":1440,"context_line":"            ho_etags \u003d [x for x in range(max_num_nodes_to_search)]"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_b2a2e2fb","line":1437,"updated":"2015-08-20 01:11:07.000000000","message":"in this test it seems that no server ever gets setup with more than one response in it\u0027s list - so maybe it\u0027d be simpler to just make the value a single StubResponse?","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"a0a99510fadb03520b47927e8e29c4626dee3e5a","unresolved":false,"context_lines":[{"line_number":1449,"context_line":"                if bad_etags:"},{"line_number":1450,"context_line":"                    resp.status \u003d 404"},{"line_number":1451,"context_line":"                    resp.headers[\u0027X-Object-Sysmeta-Ec-Etag\u0027] \u003d \u00271234\u0027"},{"line_number":1452,"context_line":"                    bad_etags.pop(0)"},{"line_number":1453,"context_line":"                return resp"},{"line_number":1454,"context_line":""},{"line_number":1455,"context_line":"            with capture_http_requests(get_response) as log:"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_32afb2e3","line":1452,"updated":"2015-08-20 01:11:07.000000000","message":"seems weird to use a list as a counter if you\u0027re not going to use the values","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"a0a99510fadb03520b47927e8e29c4626dee3e5a","unresolved":false,"context_lines":[{"line_number":1467,"context_line":"                # not build OK and we\u0027d expect to see all nodes searched"},{"line_number":1468,"context_line":"                self.assertEqual(resp.status_int, 503)"},{"line_number":1469,"context_line":"                expected_entries \u003d max_num_nodes_to_search"},{"line_number":1470,"context_line":"                expected_leftover \u003d 0"},{"line_number":1471,"context_line":""},{"line_number":1472,"context_line":"            self.assertEqual(len(log), expected_entries)"},{"line_number":1473,"context_line":"            leftover_responses \u003d []"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_324b322c","line":1470,"updated":"2015-08-20 01:11:07.000000000","message":"I\u0027m not a fan of these run_scenario tests where the assertions change depending on the parameters passed in.  I normally try to organize the boiler plate so it can do all the work and then make the assertions in the tests.  In particular this should be two different tests, test_some_mixed_etags_still_good() and test_too_many_mixed_etags_bad()","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"a0a99510fadb03520b47927e8e29c4626dee3e5a","unresolved":false,"context_lines":[{"line_number":1507,"context_line":"        (response_map[node_key(data_node)],"},{"line_number":1508,"context_line":"         response_map[node_key(parity_node)]) \u003d \\"},{"line_number":1509,"context_line":"            (response_map[node_key(parity_node)],"},{"line_number":1510,"context_line":"             response_map[node_key(data_node)])"},{"line_number":1511,"context_line":""},{"line_number":1512,"context_line":"        def get_response(req):"},{"line_number":1513,"context_line":"            server_key \u003d (req[\u0027ip\u0027], req[\u0027port\u0027])"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_b2dae24a","line":1510,"updated":"2015-08-20 01:11:07.000000000","message":"Yeah, this is really an unrelated test - maybe something like it belongs in the alt-frags patch, but I\u0027m not sure it\u0027s helpful to add it here.  Surely there is already some test that validates the order of frags in the responses doesn\u0027t matter?","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"a0a99510fadb03520b47927e8e29c4626dee3e5a","unresolved":false,"context_lines":[{"line_number":1555,"context_line":"            {\u0027obj\u0027: obj2, \u0027frag\u0027: 12},  # parity"},{"line_number":1556,"context_line":"            {\u0027obj\u0027: obj2, \u0027frag\u0027: 13},  # parity"},{"line_number":1557,"context_line":"            # {\u0027obj\u0027: obj2, \u0027frag\u0027: 2},  # handoff (not used in this test)"},{"line_number":1558,"context_line":"        ]"},{"line_number":1559,"context_line":""},{"line_number":1560,"context_line":"        all_nodes \u003d self.obj_ring.get_part_nodes(1)"},{"line_number":1561,"context_line":"        all_nodes.extend(self.obj_ring.get_more_nodes(1))"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_d2f0b693","line":1558,"updated":"2015-08-20 01:11:07.000000000","message":"I like this explicit description of what object and frags are on which node better than the count/num_bad_etags test","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"096cf623645a34642267ca3d9affabebf105cb83","unresolved":false,"context_lines":[{"line_number":1555,"context_line":"            {\u0027obj\u0027: obj2, \u0027frag\u0027: 12},  # parity"},{"line_number":1556,"context_line":"            {\u0027obj\u0027: obj2, \u0027frag\u0027: 13},  # parity"},{"line_number":1557,"context_line":"            # {\u0027obj\u0027: obj2, \u0027frag\u0027: 2},  # handoff (not used in this test)"},{"line_number":1558,"context_line":"        ]"},{"line_number":1559,"context_line":""},{"line_number":1560,"context_line":"        all_nodes \u003d self.obj_ring.get_part_nodes(1)"},{"line_number":1561,"context_line":"        all_nodes.extend(self.obj_ring.get_more_nodes(1))"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_94d692ef","line":1558,"in_reply_to":"fa1b9901_d2f0b693","updated":"2015-08-20 17:17:14.000000000","message":"+1","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"a0a99510fadb03520b47927e8e29c4626dee3e5a","unresolved":false,"context_lines":[{"line_number":1598,"context_line":"        for etag, frags in collected_responses.items():"},{"line_number":1599,"context_line":"            self.assertTrue(len(frags) \u003c\u003d self.policy.ec_ndata,"},{"line_number":1600,"context_line":"                            \u0027collected %s frags for etag %s\u0027 % ("},{"line_number":1601,"context_line":"                                len(frags), etag))"},{"line_number":1602,"context_line":""},{"line_number":1603,"context_line":"    def test_GET_mismatched_fragment_archives(self):"},{"line_number":1604,"context_line":"        segment_size \u003d self.policy.ec_segment_size"}],"source_content_type":"text/x-python","patch_set":8,"id":"fa1b9901_72b3eae9","line":1601,"updated":"2015-08-20 01:11:07.000000000","message":"so this doesn\u0027t feel like a particularly strong assertion, \"we didn\u0027t get responses for more frags than we needed\" is not *bad*, but it we could also sanity that we got more than one set of etags in the responses and which nodes responses were used.\n\nMight have to mock/stub shuffle.","commit_id":"053a5ea06270cc0c87b4ea2ec8fdca0906fc2c0f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1068f63b6ffcce334299cbfa5b4c50cb0fa3c6e2","unresolved":false,"context_lines":[{"line_number":1478,"context_line":"        # run tests with up to 5 munged etags..."},{"line_number":1479,"context_line":"        # with up to 4 the obj will build, once we hit 5 it won\u0027t"},{"line_number":1480,"context_line":"        for count in range(1, 6):"},{"line_number":1481,"context_line":"            run_scenario(count)"},{"line_number":1482,"context_line":""},{"line_number":1483,"context_line":"    def test_GET_with_misplaced_frags(self):"},{"line_number":1484,"context_line":"        segment_size \u003d self.policy.ec_segment_size"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_df7afe87","line":1481,"updated":"2015-08-25 21:27:11.000000000","message":"still don\u0027t love this test.  I think the main scenario it\u0027s covering that\u0027s not covered else where is the 503 when you finally end up with less matching etags than needed to rebuild and you hit the else assertion up above.\n\nThey way it\u0027s written you just have to \"trust\" than when count \u003d 6 it\u0027s greater than ec_nparity and that else block does get it.\n\nI think I\u0027ll try to split that up the negative assertion into a separate test and drop the positive side of this on in favor of something more like test_ec_get_disk_layout","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ce22bec9ab6e970b79f1369cb9c7b022d6740b2e","unresolved":false,"context_lines":[{"line_number":1478,"context_line":"        # run tests with up to 5 munged etags..."},{"line_number":1479,"context_line":"        # with up to 4 the obj will build, once we hit 5 it won\u0027t"},{"line_number":1480,"context_line":"        for count in range(1, 6):"},{"line_number":1481,"context_line":"            run_scenario(count)"},{"line_number":1482,"context_line":""},{"line_number":1483,"context_line":"    def test_GET_with_misplaced_frags(self):"},{"line_number":1484,"context_line":"        segment_size \u003d self.policy.ec_segment_size"}],"source_content_type":"text/x-python","patch_set":9,"id":"fa1b9901_8623d6bc","line":1481,"in_reply_to":"fa1b9901_df7afe87","updated":"2015-08-26 03:03:17.000000000","message":"Done","commit_id":"0e6f14950cbafc431903d42d9fbb8f3ac4c12ac3"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"379026b6586492ebc945fb0a2e84aa2074f5b493","unresolved":false,"context_lines":[{"line_number":1436,"context_line":"        fragment(s); create a function suitable for use with"},{"line_number":1437,"context_line":"        capture_http_requests that will accept a req object and return a"},{"line_number":1438,"context_line":"        response that will suitably fake the behavior of an object"},{"line_number":1439,"context_line":"        server who had for the given fragments on disk at the time."},{"line_number":1440,"context_line":"        \"\"\""},{"line_number":1441,"context_line":"        node_map \u003d {}"},{"line_number":1442,"context_line":"        all_nodes \u003d []"}],"source_content_type":"text/x-python","patch_set":10,"id":"da20952f_b86d70ce","line":1439,"updated":"2015-08-26 19:39:47.000000000","message":"nit : delete for?","commit_id":"fd9449358a8b20882d86a8afb2ab24d3e64fa5cd"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"379026b6586492ebc945fb0a2e84aa2074f5b493","unresolved":false,"context_lines":[{"line_number":1564,"context_line":"                                      {\u0027X-Object-Sysmeta-Ec-Etag\u0027:"},{"line_number":1565,"context_line":"                                       str(ho_etags.pop(0))})]).pop(0)"},{"line_number":1566,"context_line":"                if bad_etags:"},{"line_number":1567,"context_line":"                    resp.status \u003d 404"},{"line_number":1568,"context_line":"                    resp.headers[\u0027X-Object-Sysmeta-Ec-Etag\u0027] \u003d \u00271234\u0027"},{"line_number":1569,"context_line":"                    bad_etags.pop(0)"},{"line_number":1570,"context_line":"                return resp"}],"source_content_type":"text/x-python","patch_set":10,"id":"da20952f_f88e8826","line":1567,"updated":"2015-08-26 19:39:47.000000000","message":"why 404? isn\u0027t the idea to return a 200 but with mismatched etag?","commit_id":"fd9449358a8b20882d86a8afb2ab24d3e64fa5cd"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9a2cb5c93d8e17c590a902d7d876d21d18317fe7","unresolved":false,"context_lines":[{"line_number":1564,"context_line":"                                      {\u0027X-Object-Sysmeta-Ec-Etag\u0027:"},{"line_number":1565,"context_line":"                                       str(ho_etags.pop(0))})]).pop(0)"},{"line_number":1566,"context_line":"                if bad_etags:"},{"line_number":1567,"context_line":"                    resp.status \u003d 404"},{"line_number":1568,"context_line":"                    resp.headers[\u0027X-Object-Sysmeta-Ec-Etag\u0027] \u003d \u00271234\u0027"},{"line_number":1569,"context_line":"                    bad_etags.pop(0)"},{"line_number":1570,"context_line":"                return resp"}],"source_content_type":"text/x-python","patch_set":10,"id":"da20952f_96cabd4e","line":1567,"in_reply_to":"da20952f_f88e8826","updated":"2015-08-26 19:53:58.000000000","message":"bah, I meant to delete this :D","commit_id":"fd9449358a8b20882d86a8afb2ab24d3e64fa5cd"}]}
