)]}'
{"nova/exception.py":[{"author":{"_account_id":11564,"name":"Chris Dent","email":"cdent@anticdent.org","username":"chdent"},"change_message_id":"d7d70a0747c1e3910f7227659bb9f246e5506129","unresolved":false,"context_lines":[{"line_number":2103,"context_line":""},{"line_number":2104,"context_line":"class PlacementAPIConflict(NovaException):"},{"line_number":2105,"context_line":"    \"\"\"Any 409 error from placement APIs should use (a subclass of) this"},{"line_number":2106,"context_line":"    exception."},{"line_number":2107,"context_line":"    \"\"\""},{"line_number":2108,"context_line":"    msg_fmt \u003d _(\"A conflict was encountered attempting to invoke the \""},{"line_number":2109,"context_line":"                \"placement API at URL %(url)s: %(error)s\")"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_2fb60ddb","line":2106,"updated":"2018-01-15 11:47:53.000000000","message":"I think this docstring is where my problem with the previous path is. PlacementAPIConflict should only bubble in the case of a resolvable Placemennt API Conflict (which is identified by a generation conflict).\n\nAnything else introduces ambiguity in the handling, which we don\u0027t want. We want to be able to say \"if we see an exception of this class, in any context, we know there is one thing to do, without overt inspection.\"","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"2dd685bf48a817a56286f77e82fe93e9a95bffbf","unresolved":false,"context_lines":[{"line_number":2103,"context_line":""},{"line_number":2104,"context_line":"class PlacementAPIConflict(NovaException):"},{"line_number":2105,"context_line":"    \"\"\"Any 409 error from placement APIs should use (a subclass of) this"},{"line_number":2106,"context_line":"    exception."},{"line_number":2107,"context_line":"    \"\"\""},{"line_number":2108,"context_line":"    msg_fmt \u003d _(\"A conflict was encountered attempting to invoke the \""},{"line_number":2109,"context_line":"                \"placement API at URL %(url)s: %(error)s\")"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_66e75742","line":2106,"in_reply_to":"7f96bb07_2fb60ddb","updated":"2018-01-15 15:56:06.000000000","message":"Right you are.  I abandoned the previous patch and incorporated PlacementAPIConflict into this one.","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":11564,"name":"Chris Dent","email":"cdent@anticdent.org","username":"chdent"},"change_message_id":"d7d70a0747c1e3910f7227659bb9f246e5506129","unresolved":false,"context_lines":[{"line_number":2111,"context_line":""},{"line_number":2112,"context_line":"class ResourceProviderCreateConflict(PlacementAPIConflict):"},{"line_number":2113,"context_line":"    msg_fmt \u003d _(\"A conflict was encountered attempting to create resource \""},{"line_number":2114,"context_line":"                \"provider with name %(name)s and UUID %(uuid)s: %(error)s\")"},{"line_number":2115,"context_line":""},{"line_number":2116,"context_line":""},{"line_number":2117,"context_line":"class ResourceProviderUpdateConflict(PlacementAPIConflict):"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_0fe8f1b1","line":2114,"updated":"2018-01-15 11:47:53.000000000","message":"Thus should not be PlacementAPIConflict.","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"2dd685bf48a817a56286f77e82fe93e9a95bffbf","unresolved":false,"context_lines":[{"line_number":2111,"context_line":""},{"line_number":2112,"context_line":"class ResourceProviderCreateConflict(PlacementAPIConflict):"},{"line_number":2113,"context_line":"    msg_fmt \u003d _(\"A conflict was encountered attempting to create resource \""},{"line_number":2114,"context_line":"                \"provider with name %(name)s and UUID %(uuid)s: %(error)s\")"},{"line_number":2115,"context_line":""},{"line_number":2116,"context_line":""},{"line_number":2117,"context_line":"class ResourceProviderUpdateConflict(PlacementAPIConflict):"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_86e2a32e","line":2114,"in_reply_to":"7f96bb07_0fe8f1b1","updated":"2018-01-15 15:56:06.000000000","message":"Done","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":11564,"name":"Chris Dent","email":"cdent@anticdent.org","username":"chdent"},"change_message_id":"d7d70a0747c1e3910f7227659bb9f246e5506129","unresolved":false,"context_lines":[{"line_number":2117,"context_line":"class ResourceProviderUpdateConflict(PlacementAPIConflict):"},{"line_number":2118,"context_line":"    \"\"\"A 409 from attempting to update an existing provider record or its"},{"line_number":2119,"context_line":"    associated data (aggregates, traits, etc.).  Usually caused by generation"},{"line_number":2120,"context_line":"    mismatch."},{"line_number":2121,"context_line":"    \"\"\""},{"line_number":2122,"context_line":"    msg_fmt \u003d _(\"A conflict was encountered attempting to update resource \""},{"line_number":2123,"context_line":"                \"provider %(uuid)s (generation %(generation)d): %(error)s\")"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_4fdef911","line":2120,"updated":"2018-01-15 11:47:53.000000000","message":"And this should only be if s/Usually//","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"2dd685bf48a817a56286f77e82fe93e9a95bffbf","unresolved":false,"context_lines":[{"line_number":2117,"context_line":"class ResourceProviderUpdateConflict(PlacementAPIConflict):"},{"line_number":2118,"context_line":"    \"\"\"A 409 from attempting to update an existing provider record or its"},{"line_number":2119,"context_line":"    associated data (aggregates, traits, etc.).  Usually caused by generation"},{"line_number":2120,"context_line":"    mismatch."},{"line_number":2121,"context_line":"    \"\"\""},{"line_number":2122,"context_line":"    msg_fmt \u003d _(\"A conflict was encountered attempting to update resource \""},{"line_number":2123,"context_line":"                \"provider %(uuid)s (generation %(generation)d): %(error)s\")"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_a6c6bf99","line":2120,"in_reply_to":"7f96bb07_4fdef911","updated":"2018-01-15 15:56:06.000000000","message":"Done","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"}],"nova/scheduler/client/report.py":[{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"b9091705c538c21dbdf088ec416980d8751fb3e8","unresolved":false,"context_lines":[{"line_number":996,"context_line":"            self._delete_inventory(rp_uuid)"},{"line_number":997,"context_line":""},{"line_number":998,"context_line":"    @safe_connect"},{"line_number":999,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1000,"context_line":"        url \u003d \u0027/resource_providers/%s/traits\u0027 % rp_uuid"},{"line_number":1001,"context_line":"        # NOTE(efried): Don\u0027t use the DELETE API when traits is empty, because"},{"line_number":1002,"context_line":"        # that guy doesn\u0027t return content, and we need to update the cached"}],"source_content_type":"text/x-python","patch_set":1,"id":"7f96bb07_a72068f9","line":999,"updated":"2018-01-10 17:43:17.000000000","message":"This is definitely simpler than my patch [1]. There are a few things that the dumb copy from set_inventory_for_provider approach has that this doesn\u0027t, just want to make sure they\u0027ve been omitted consciously.\n\n* Do we need to call self._ensure_resource_provider?\n* Do we need to ensure custom traits exist as in [2]?\n\n[1] https://review.openstack.org/#/c/532539/1/nova/scheduler/client/report.py\n[2] https://review.openstack.org/#/c/532539/1/nova/scheduler/client/report.py@1231","commit_id":"160901e8d953ab07917947d36b2bd7eb386c7e87"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"92bb49013b9dc62a8e38fed1719b7fa17aa349d3","unresolved":false,"context_lines":[{"line_number":996,"context_line":"            self._delete_inventory(rp_uuid)"},{"line_number":997,"context_line":""},{"line_number":998,"context_line":"    @safe_connect"},{"line_number":999,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1000,"context_line":"        url \u003d \u0027/resource_providers/%s/traits\u0027 % rp_uuid"},{"line_number":1001,"context_line":"        # NOTE(efried): Don\u0027t use the DELETE API when traits is empty, because"},{"line_number":1002,"context_line":"        # that guy doesn\u0027t return content, and we need to update the cached"}],"source_content_type":"text/x-python","patch_set":1,"id":"7f96bb07_e51d05be","line":999,"in_reply_to":"7f96bb07_a72068f9","updated":"2018-01-10 21:23:01.000000000","message":"\u003e * Do we need to call self._ensure_resource_provider?\n\nI deliberately didn\u0027t do that here, under the assumption that you only call this on a provider you know exists.  I recognize that\u0027s brittle because we (don\u0027t dis)allow multiple sources of ownership/control for a given provider.  (Although _ensure_resource_provider doesn\u0027t actually close that window completely either.)  But I think, at least for now, we can accept that possibility, understanding that it will cause an exception to be raised up.\n\nI will document this in the docstring.\n\n(BTW, one of the reasons I didn\u0027t want to _ensure_resource_provider is so I didn\u0027t have to duplicate the name and parent_provider_uuid args to the method.)\n\n \u003e * Do we need to ensure custom traits exist as in [2]?\n\nYes, good call.  I\u0027ll update accordingly.","commit_id":"160901e8d953ab07917947d36b2bd7eb386c7e87"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"b9091705c538c21dbdf088ec416980d8751fb3e8","unresolved":false,"context_lines":[{"line_number":1013,"context_line":"            self._provider_tree.update_traits(rp_uuid, json[\u0027traits\u0027],"},{"line_number":1014,"context_line":"                                              generation\u003djson[\u0027generation\u0027])"},{"line_number":1015,"context_line":"            return"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"        # Some error occurred; log it"},{"line_number":1018,"context_line":"        msg \u003d (\"[%(placement_req_id)s] Failed to update traits to \""},{"line_number":1019,"context_line":"               \"[%(traits)s] for resource provider with UUID %(uuid)s.  Got \""}],"source_content_type":"text/x-python","patch_set":1,"id":"7f96bb07_677820f8","line":1016,"updated":"2018-01-10 17:43:17.000000000","message":"Do we need to handle the case where microversion 1.6 is not available?","commit_id":"160901e8d953ab07917947d36b2bd7eb386c7e87"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"92bb49013b9dc62a8e38fed1719b7fa17aa349d3","unresolved":false,"context_lines":[{"line_number":1013,"context_line":"            self._provider_tree.update_traits(rp_uuid, json[\u0027traits\u0027],"},{"line_number":1014,"context_line":"                                              generation\u003djson[\u0027generation\u0027])"},{"line_number":1015,"context_line":"            return"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"        # Some error occurred; log it"},{"line_number":1018,"context_line":"        msg \u003d (\"[%(placement_req_id)s] Failed to update traits to \""},{"line_number":1019,"context_line":"               \"[%(traits)s] for resource provider with UUID %(uuid)s.  Got \""}],"source_content_type":"text/x-python","patch_set":1,"id":"7f96bb07_c57569fc","line":1016,"in_reply_to":"7f96bb07_677820f8","updated":"2018-01-10 21:23:01.000000000","message":"I *think* we\u0027re guaranteed to have microversion 1.10 in queens, though I can\u0027t remember where that guarantee comes from.\n\nWe should actually be doing microversion discovery once, in _create_client.  But I\u0027ll check on this and make sure we\u0027re doing The Right Thing.","commit_id":"160901e8d953ab07917947d36b2bd7eb386c7e87"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"7e61ecfe3cb8f12470f028efa084f5d033f624cc","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_25bd14b7","line":1020,"updated":"2018-01-11 17:45:41.000000000","message":"Even if we don\u0027t filter out the standard traits from the os_traits library here, we could check only those traits with the CUSTOM_ prefix to simplify the query and response. We\u0027ll then fail later on when PUTting the traits on the RP if non-existent standard traits were requested.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"9f103011120c52afa626bccdf7e76371bc135c19","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_771bbd1a","line":1020,"in_reply_to":"7f96bb07_08282f84","updated":"2018-01-12 09:29:22.000000000","message":"I guess it depends on the expected number of traits. With one or two per node it\u0027s unlikely to be an issue. Imagine we had 1000 ironic nodes, each with the maximum of 50 traits (excessive, perhaps, but consider they may be auto-assigned by a discovery service such as ironic inspector). Would the resulting DB query for each provider become a little too heavy?\n\nSome alternative strategies:\n* Ask for forgiveness rather than permission\n* Keep a cache of the registered traits\n* Batch up compute nodes and lean on the magic of set() theory\n\nPerhaps this is something that could be optimised at a later date.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"a830963fb2b0e99fd615a5ba6fd089e37a174c80","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_24008867","line":1020,"in_reply_to":"7f96bb07_16bb490e","updated":"2018-01-13 08:50:21.000000000","message":"For get_traits, I\u0027ll be using set_traits_for_provider directly from the resource tracker. There won\u0027t be a provider tree at that level to check (without coupling to the get_provider_tree work), so any checking must be done in set_traits_for_provider. I guess I could implement a wrapper function in the report client that does that checking.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"67dc690a619182155a3214a65e58d9dbdbd46537","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_3faae3d1","line":1020,"in_reply_to":"7f96bb07_16db54ed","updated":"2018-01-12 16:29:27.000000000","message":"I think in the get_traits() case we won\u0027t be checking the provider tree (at least not as it stands). Still, your test sounds promising.\n\nTraits should be changing only rarely on ironic nodes. There will be quite a spike as the compute service starts though, as a single compute service may be managing hundreds of nodes that all end up syncing traits.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"62b1bd836d41a6607e1697f804dbad4ddcc2074e","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_ed128358","line":1020,"in_reply_to":"7f96bb07_24008867","updated":"2018-01-13 18:14:20.000000000","message":"Yeah.  Though you have me wondering  whether set_traits_for_provider should actually do it.  I.e. if the generation you pass me is the same as the generation in the provider tree AND have_traits_changed is false, I don\u0027t bother making the API call.  Have to think through what happens if the generation doesn\u0027t match.  And whether I want a \u0027force\u0027 flag.  Food for thought.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"4b34035e6d5a08a2470f6e54cca50aaf6b40768b","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_08282f84","line":1020,"in_reply_to":"7f96bb07_25bd14b7","updated":"2018-01-11 18:08:14.000000000","message":"We *could*, but this way is more future-proof (in case placement comes to allow creation of traits with some other namespace prefix).\n\nAs noted below, we\u0027ll probably only get to the PUTs once, if that, the first time the compute process is stood up, so I\u0027m not really worried about the performance hit of doing an extra API call to get that error.\n\nThoughts?","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"eeebc7357f367d6edf1c24ac4f29eacef5295968","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_16bb490e","line":1020,"in_reply_to":"7f96bb07_3faae3d1","updated":"2018-01-12 22:40:53.000000000","message":"\u003e I think in the get_traits() case we won\u0027t be checking the provider\n \u003e tree (at least not as it stands).\n\nUm, you should be.  You\u0027ll be wanting to keep it up to date, for starters.  But also for the sake of the efficiency I described, you\u0027ll want to restrict writing the provider traits back to placement to occasions when that\u0027s actually necessary (i.e. reportclient._provider_tree.have_traits_changed()).\n\n \u003e Traits should be changing only rarely on ironic nodes. There will\n \u003e be quite a spike as the compute service starts though, as a single\n \u003e compute service may be managing hundreds of nodes that all end up\n \u003e syncing traits.\n\nThere\u0027s really no answer for this as the API stands.  If this is identified as a serious problem, we\u0027ll start wanting to look at batched PUTs.  A local cache is well and good, but it has to be primed at some point.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"a1e3dbb4f8cae3ae454bc60744252b0fc5f62451","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_16db54ed","line":1020,"in_reply_to":"7f96bb07_771bbd1a","updated":"2018-01-12 15:14:43.000000000","message":"\u003e * Keep a cache of the registered traits\n\nThis.\n\n \u003e Perhaps this is something that could be optimised at a later date.\n\nBut definitely this.  I\u0027ll go a step further and say let\u0027s not solve a problem until there is one.\n\nTo expand on the performance analysis, we should only be invoking set_traits_for_provider in the first place if the traits reported by the compute driver differ from what\u0027s in our cache (self._provider_tree.have_traits_changed(...)).  Which again should happen on compute startup, and then quite rarely thereafter.  How frequently do you see traits being changed on an ironic node?\n\nOut of interest, I just added 10,000 custom traits to my placement DB, and whereas getting the whole list gets appreciably slower, getting 50 still performs on the same order as getting just one (tens of ms on my none-too-impressive compute host running placement locally).","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"2dd685bf48a817a56286f77e82fe93e9a95bffbf","unresolved":false,"context_lines":[{"line_number":1017,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":1018,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":1019,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":1020,"context_line":"        # they\u0027ll get."},{"line_number":1021,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1022,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1023,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"}],"source_content_type":"text/x-python","patch_set":2,"id":"7f96bb07_66b9972c","line":1020,"in_reply_to":"7f96bb07_ed128358","updated":"2018-01-15 15:56:06.000000000","message":"I added a TODO to consider this as an optimization.  In the meantime, what\u0027s there works; it just may be making some redundant API calls.","commit_id":"3843c9122d109fbf1e4d804089bb817e161771a1"},{"author":{"_account_id":11564,"name":"Chris Dent","email":"cdent@anticdent.org","username":"chdent"},"change_message_id":"d7d70a0747c1e3910f7227659bb9f246e5506129","unresolved":false,"context_lines":[{"line_number":1000,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1001,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1002,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"},{"line_number":1003,"context_line":"            # Might be neat to have a batch create.  But creating multiple"},{"line_number":1004,"context_line":"            # traits will generally happen once, at initial startup, if at all."},{"line_number":1005,"context_line":"            for trait in traits_to_create:"},{"line_number":1006,"context_line":"                resp \u003d self.put(\u0027/traits/\u0027 + trait, None, version\u003d\u00271.6\u0027)"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_6f99751d","line":1003,"updated":"2018-01-15 11:47:53.000000000","message":"Lots of little requests are good, at least ones that don\u0027t involve generation handling, as far as the server is concerned because in aggregate from multiple sources they can be effectively horizontally scaled amongst multiple placement servers (which is the way placement is supposed to be deployed).\n\nSame for GETs too.\n\nI\u0027d almost be inclined to not do the set handling here and instead filter out the non CUSTOM_ prefixed and PUT every CUSTOM_*. If it already exists, it behaves exactly the same as a GET.\n\nBut then, I\u0027m a fan of lots of little requests.","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"2dd685bf48a817a56286f77e82fe93e9a95bffbf","unresolved":false,"context_lines":[{"line_number":1000,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1001,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1002,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"},{"line_number":1003,"context_line":"            # Might be neat to have a batch create.  But creating multiple"},{"line_number":1004,"context_line":"            # traits will generally happen once, at initial startup, if at all."},{"line_number":1005,"context_line":"            for trait in traits_to_create:"},{"line_number":1006,"context_line":"                resp \u003d self.put(\u0027/traits/\u0027 + trait, None, version\u003d\u00271.6\u0027)"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_e600476a","line":1003,"in_reply_to":"7f96bb07_6f99751d","updated":"2018-01-15 15:56:06.000000000","message":"\u003e horizontally scaled amongst multiple placement servers \n\nMultiple placement servers handling the same data(base)?  Then they\u0027ll be serialized on that database anyway, and we haven\u0027t gained anything by distributing the service, nah?\n\n \u003e filter out the non CUSTOM_ prefixed\n\nMike and I had that discussion in an earlier patch set: see if you agree with (or can at least accept) my reasoning: https://review.openstack.org/#/c/532564/2/nova/scheduler/client/report.py@1020\n\n\u003e PUT every CUSTOM_*. If it already exists, it behaves exactly the same as a GET.\n\nExcept for having had to make an unnecessary API call.  Sorry, I can\u0027t get on board with this.  These placement API requests are quick, but not negligible.  If we can cut some out without significantly increasing complexity, IMO that\u0027s what we should do.","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":11564,"name":"Chris Dent","email":"cdent@anticdent.org","username":"chdent"},"change_message_id":"c1f78a7c0e2795f9eb46f06512f9717d845902d2","unresolved":false,"context_lines":[{"line_number":1000,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":1001,"context_line":"        if resp.status_code \u003d\u003d 200:"},{"line_number":1002,"context_line":"            traits_to_create \u003d set(traits) - set(resp.json()[\u0027traits\u0027])"},{"line_number":1003,"context_line":"            # Might be neat to have a batch create.  But creating multiple"},{"line_number":1004,"context_line":"            # traits will generally happen once, at initial startup, if at all."},{"line_number":1005,"context_line":"            for trait in traits_to_create:"},{"line_number":1006,"context_line":"                resp \u003d self.put(\u0027/traits/\u0027 + trait, None, version\u003d\u00271.6\u0027)"}],"source_content_type":"text/x-python","patch_set":5,"id":"7f96bb07_6693d7fb","line":1003,"in_reply_to":"7f96bb07_e600476a","updated":"2018-01-15 16:02:31.000000000","message":"That the data store is serializing is an implementation detail. Maybe even a bug.\n\nAnyway, my comments are just chat for the sake of discussion and reflection upon the state of the universe, I\u0027m fine with this. Perfection is the enemy and all that.","commit_id":"8db3c53bf11245af7b30f89916fd511dd7229de2"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"863992c4d08ff0a21e4daece26ea56ddab51b15e","unresolved":false,"context_lines":[{"line_number":988,"context_line":"        # NOTE(efried): We don\u0027t attempt to filter based on our local idea of"},{"line_number":989,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":990,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":991,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":992,"context_line":"        # they\u0027ll get."},{"line_number":993,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":994,"context_line":"        if resp.status_code \u003d\u003d 200:"}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_d2813f4c","line":991,"range":{"start_line":991,"start_character":10,"end_line":991,"end_character":20},"updated":"2018-01-15 21:51:49.000000000","message":"you mean standard?","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"da501ac5755041922705338eed48143003e2c615","unresolved":false,"context_lines":[{"line_number":988,"context_line":"        # NOTE(efried): We don\u0027t attempt to filter based on our local idea of"},{"line_number":989,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":990,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":991,"context_line":"        # non-CUSTOM trait, they deserve the TraitCreationFailed exception"},{"line_number":992,"context_line":"        # they\u0027ll get."},{"line_number":993,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"},{"line_number":994,"context_line":"        if resp.status_code \u003d\u003d 200:"}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_f24c7b9f","line":991,"range":{"start_line":991,"start_character":10,"end_line":991,"end_character":20},"in_reply_to":"7f96bb07_d2813f4c","updated":"2018-01-15 22:22:14.000000000","message":"okay.","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"863992c4d08ff0a21e4daece26ea56ddab51b15e","unresolved":false,"context_lines":[{"line_number":997,"context_line":"            # traits will generally happen once, at initial startup, if at all."},{"line_number":998,"context_line":"            for trait in traits_to_create:"},{"line_number":999,"context_line":"                resp \u003d self.put(\u0027/traits/\u0027 + trait, None, version\u003d\u00271.6\u0027)"},{"line_number":1000,"context_line":"                # Response.__bool__ returns False for non-successful statuses"},{"line_number":1001,"context_line":"                if not resp:"},{"line_number":1002,"context_line":"                    raise exception.TraitCreationFailed(name\u003dtrait,"},{"line_number":1003,"context_line":"                                                        error\u003dresp.text)"}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_32b9832d","line":1000,"range":{"start_line":1000,"start_character":16,"end_line":1000,"end_character":77},"updated":"2018-01-15 21:51:49.000000000","message":"not sure this is necessary any more... doing if not resp: is pretty much standard practice at this point.","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"da501ac5755041922705338eed48143003e2c615","unresolved":false,"context_lines":[{"line_number":997,"context_line":"            # traits will generally happen once, at initial startup, if at all."},{"line_number":998,"context_line":"            for trait in traits_to_create:"},{"line_number":999,"context_line":"                resp \u003d self.put(\u0027/traits/\u0027 + trait, None, version\u003d\u00271.6\u0027)"},{"line_number":1000,"context_line":"                # Response.__bool__ returns False for non-successful statuses"},{"line_number":1001,"context_line":"                if not resp:"},{"line_number":1002,"context_line":"                    raise exception.TraitCreationFailed(name\u003dtrait,"},{"line_number":1003,"context_line":"                                                        error\u003dresp.text)"}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_f231db0c","line":1000,"range":{"start_line":1000,"start_character":16,"end_line":1000,"end_character":77},"in_reply_to":"7f96bb07_32b9832d","updated":"2018-01-15 22:22:14.000000000","message":"No doubt.  But I saw it for the first time a few days ago and it threw me for such a loop that I opened a bug on it [1].  Anyway, removed.\n\n[1] https://bugs.launchpad.net/nova/+bug/1742311","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"db0e205566cb3149cd1ffd28525de01f6c4b4f49","unresolved":false,"context_lines":[{"line_number":1015,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"    @safe_connect"},{"line_number":1018,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1019,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1020,"context_line":""},{"line_number":1021,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_0f51c00e","line":1018,"range":{"start_line":1018,"start_character":55,"end_line":1018,"end_character":65},"updated":"2018-01-15 21:10:53.000000000","message":"Say, what do folks think of the idea of making this arg optional?  If omitted, the method will grab it from the cached provider tree.","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"863992c4d08ff0a21e4daece26ea56ddab51b15e","unresolved":false,"context_lines":[{"line_number":1015,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"    @safe_connect"},{"line_number":1018,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1019,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1020,"context_line":""},{"line_number":1021,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_12d6c750","line":1018,"range":{"start_line":1018,"start_character":55,"end_line":1018,"end_character":65},"in_reply_to":"7f96bb07_0f51c00e","updated":"2018-01-15 21:51:49.000000000","message":"I don\u0027t think there should be a generation parameter *at all*. I think the reportclient\u0027s _provider_tree should *always* be consulted for the currently-known generation.","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"da501ac5755041922705338eed48143003e2c615","unresolved":false,"context_lines":[{"line_number":1015,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"    @safe_connect"},{"line_number":1018,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1019,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1020,"context_line":""},{"line_number":1021,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_922097b2","line":1018,"range":{"start_line":1018,"start_character":55,"end_line":1018,"end_character":65},"in_reply_to":"7f96bb07_12d6c750","updated":"2018-01-15 22:22:14.000000000","message":"Dig.\n\nIs it okay if I remove it later in the series, after I have ProviderTree.snapshot?  Otherwise I have to reorder the patches (again).","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"6a315e95d73f031831a51390ae5a056d3b126123","unresolved":false,"context_lines":[{"line_number":1015,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"    @safe_connect"},{"line_number":1018,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1019,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1020,"context_line":""},{"line_number":1021,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_f1a77c0a","line":1018,"range":{"start_line":1018,"start_character":55,"end_line":1018,"end_character":65},"in_reply_to":"7f96bb07_1a7d6a7e","updated":"2018-01-16 14:45:51.000000000","message":"Okay.  I removed it in the ProviderTree.snapshot patch.  But I\u0027ll reorder the patches so we can get rid of it here.  It\u0027s the Right Thing To Do, painful though it may be.","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"cffb9155c62901b91dc5c1a3a2c1a202ffe2f32c","unresolved":false,"context_lines":[{"line_number":1015,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1016,"context_line":""},{"line_number":1017,"context_line":"    @safe_connect"},{"line_number":1018,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1019,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1020,"context_line":""},{"line_number":1021,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":6,"id":"7f96bb07_1a7d6a7e","line":1018,"range":{"start_line":1018,"start_character":55,"end_line":1018,"end_character":65},"in_reply_to":"7f96bb07_922097b2","updated":"2018-01-16 11:05:54.000000000","message":"For the ironic get_traits work, this method will be called from the resource tracker, where there the generation is not known. Currently I have to wrap this method with another to make it usable, so I\u0027d much rather the generation argument was removed or made optional.","commit_id":"0ddfb3383db032d5081502e7aaba5c8d3b009d19"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"fea25af99ff5fbd299da53965258cfa69fe95dc1","unresolved":false,"context_lines":[{"line_number":1014,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1015,"context_line":""},{"line_number":1016,"context_line":"    @safe_connect"},{"line_number":1017,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1018,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1019,"context_line":""},{"line_number":1020,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":7,"id":"7f96bb07_1ad9ea7d","line":1017,"updated":"2018-01-16 11:08:11.000000000","message":"Should there be an identically named method in nova.scheduler.client?","commit_id":"bdbe7284647c6a7a3778f7745c9b4f4d3637a883"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"6b2824017eeb94e0d9a805899d74b43cdbc2ac66","unresolved":false,"context_lines":[{"line_number":1014,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1015,"context_line":""},{"line_number":1016,"context_line":"    @safe_connect"},{"line_number":1017,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1018,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1019,"context_line":""},{"line_number":1020,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":7,"id":"7f96bb07_76b3624e","line":1017,"in_reply_to":"7f96bb07_1ad9ea7d","updated":"2018-01-16 14:18:34.000000000","message":"nah... we haven\u0027t kept up with that and it\u0027s just a useless layer of abstraction.","commit_id":"bdbe7284647c6a7a3778f7745c9b4f4d3637a883"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"b55cd9c41d61106b6e850150827cdac8f15b82df","unresolved":false,"context_lines":[{"line_number":1014,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1015,"context_line":""},{"line_number":1016,"context_line":"    @safe_connect"},{"line_number":1017,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1018,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1019,"context_line":""},{"line_number":1020,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":7,"id":"7f96bb07_d154c000","line":1017,"in_reply_to":"7f96bb07_76b3624e","updated":"2018-01-16 14:44:17.000000000","message":"Hum, I\u0027ve been keeping up with it... sometimes (clearly not in this patch).  If it\u0027s a useless layer of abstraction, can we get rid of it completely?","commit_id":"bdbe7284647c6a7a3778f7745c9b4f4d3637a883"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"03a45c6b587e3ee84e665e7d6dea66abc15e3584","unresolved":false,"context_lines":[{"line_number":1014,"context_line":"        raise exception.TraitRetrievalFailed(error\u003dresp.text)"},{"line_number":1015,"context_line":""},{"line_number":1016,"context_line":"    @safe_connect"},{"line_number":1017,"context_line":"    def set_traits_for_provider(self, rp_uuid, traits, generation):"},{"line_number":1018,"context_line":"        \"\"\"Replace a provider\u0027s traits with those specified."},{"line_number":1019,"context_line":""},{"line_number":1020,"context_line":"        The provider must exist - this method does not attempt to create it."}],"source_content_type":"text/x-python","patch_set":7,"id":"7f96bb07_11eb0813","line":1017,"in_reply_to":"7f96bb07_d154c000","updated":"2018-01-16 14:46:44.000000000","message":"I\u0027d love to. Yes. just not a priority right now..","commit_id":"bdbe7284647c6a7a3778f7745c9b4f4d3637a883"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"c3db7879e958aabb03919d5f0fee63825319d79f","unresolved":false,"context_lines":[{"line_number":1043,"context_line":"        # that guy doesn\u0027t return content, and we need to update the cached"},{"line_number":1044,"context_line":"        # provider tree with the new generation."},{"line_number":1045,"context_line":"        traits \u003d traits or []"},{"line_number":1046,"context_line":"        generation \u003d self._provider_tree.snapshot(rp_uuid).generation"},{"line_number":1047,"context_line":"        payload \u003d {"},{"line_number":1048,"context_line":"            \u0027resource_provider_generation\u0027: generation,"},{"line_number":1049,"context_line":"            \u0027traits\u0027: traits,"}],"source_content_type":"text/x-python","patch_set":9,"id":"7f96bb07_6dbdc800","line":1046,"updated":"2018-01-16 18:25:33.000000000","message":"This kinda hurts, making a whole deep copy of the provider\u0027s attrs just to throw away everything but the generation int.  May want to consider implementing an accessor just for the generation, à la https://review.openstack.org/#/c/532922/","commit_id":"46ff24f638fa6888e27a626b14686727f4e5225a"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"21b531060c0802a9d808da67a309c54e85fbfb01","unresolved":false,"context_lines":[{"line_number":1043,"context_line":"        # that guy doesn\u0027t return content, and we need to update the cached"},{"line_number":1044,"context_line":"        # provider tree with the new generation."},{"line_number":1045,"context_line":"        traits \u003d traits or []"},{"line_number":1046,"context_line":"        generation \u003d self._provider_tree.snapshot(rp_uuid).generation"},{"line_number":1047,"context_line":"        payload \u003d {"},{"line_number":1048,"context_line":"            \u0027resource_provider_generation\u0027: generation,"},{"line_number":1049,"context_line":"            \u0027traits\u0027: traits,"}],"source_content_type":"text/x-python","patch_set":9,"id":"7f96bb07_c57b4589","line":1046,"in_reply_to":"7f96bb07_6dbdc800","updated":"2018-01-18 16:50:28.000000000","message":"Having taken children out of ProviderData (formerly ProviderSnapshot), this isn\u0027t as painful anymore.","commit_id":"46ff24f638fa6888e27a626b14686727f4e5225a"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"9a92d5bb129d7d0342ef8ff463cae7964537a64f","unresolved":false,"context_lines":[{"line_number":985,"context_line":""},{"line_number":986,"context_line":"        # Query for all the requested traits.  Whichever ones we *don\u0027t* get"},{"line_number":987,"context_line":"        # back, we need to create."},{"line_number":988,"context_line":"        # NOTE(efried): We don\u0027t attempt to filter based on our local idea of"},{"line_number":989,"context_line":"        # standard traits, which may not be in sync with what the placement"},{"line_number":990,"context_line":"        # service knows.  If the caller tries to ensure a nonexistent"},{"line_number":991,"context_line":"        # \"standard\" trait, they deserve the TraitCreationFailed exception"},{"line_number":992,"context_line":"        # they\u0027ll get."},{"line_number":993,"context_line":"        resp \u003d self.get(\u0027/traits?name\u003din:\u0027 + \u0027,\u0027.join(traits), version\u003d\u00271.6\u0027)"}],"source_content_type":"text/x-python","patch_set":10,"id":"7f96bb07_b9d5e845","line":990,"range":{"start_line":988,"start_character":10,"end_line":990,"end_character":24},"updated":"2018-01-19 15:24:35.000000000","message":"ack. placement is the single source of truth in that matter.","commit_id":"c3394eb1aba7469cca536f62bbd8543f617fee92"}],"nova/tests/unit/scheduler/client/test_report.py":[{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"9a92d5bb129d7d0342ef8ff463cae7964537a64f","unresolved":false,"context_lines":[{"line_number":2013,"context_line":"        self.ks_adap_mock.put.return_value \u003d mock.Mock(status_code\u003d503)"},{"line_number":2014,"context_line":"        self.assertRaises("},{"line_number":2015,"context_line":"            exception.ResourceProviderUpdateFailed,"},{"line_number":2016,"context_line":"            self.client.set_traits_for_provider, uuids.rp, traits)"},{"line_number":2017,"context_line":""},{"line_number":2018,"context_line":""},{"line_number":2019,"context_line":"class TestAssociations(SchedulerReportClientTestCase):"}],"source_content_type":"text/x-python","patch_set":10,"id":"7f96bb07_b9d64827","line":2016,"updated":"2018-01-19 15:24:35.000000000","message":"++ great tests.","commit_id":"c3394eb1aba7469cca536f62bbd8543f617fee92"}]}
