)]}'
{"nova/tests/unit/virt/ironic/test_patcher.py":[{"author":{"_account_id":782,"name":"John Garbutt","email":"john@johngarbutt.com","username":"johngarbutt"},"change_message_id":"268bd5e691ca4ec28b507dd0d98ba2ad8a08084b","unresolved":false,"context_lines":[{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_generic_get_deploy_patch_traits(self):"},{"line_number":119,"context_line":"        node \u003d ironic_utils.get_test_node(driver\u003d\u0027fake\u0027)"},{"line_number":120,"context_line":"        self.flavor[\u0027extra_specs\u0027][\u0027trait:CUSTOM_FOO\u0027] \u003d \u0027required\u0027"},{"line_number":121,"context_line":"        expected \u003d [{\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":122,"context_line":"                     \u0027value\u0027: \u0027[\"CUSTOM_FOO\"]\u0027,"},{"line_number":123,"context_line":"                     \u0027op\u0027: \u0027add\u0027}]"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_39a9b1de","line":120,"updated":"2018-01-22 11:19:58.000000000","message":"Nit: possibly worth having two traits here, for completeness.","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"c02e3e914eb8e0349b9b8e663c70ca547d842dd6","unresolved":false,"context_lines":[{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_generic_get_deploy_patch_traits(self):"},{"line_number":119,"context_line":"        node \u003d ironic_utils.get_test_node(driver\u003d\u0027fake\u0027)"},{"line_number":120,"context_line":"        self.flavor[\u0027extra_specs\u0027][\u0027trait:CUSTOM_FOO\u0027] \u003d \u0027required\u0027"},{"line_number":121,"context_line":"        expected \u003d [{\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":122,"context_line":"                     \u0027value\u0027: \u0027[\"CUSTOM_FOO\"]\u0027,"},{"line_number":123,"context_line":"                     \u0027op\u0027: \u0027add\u0027}]"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_9f5f77e4","line":120,"in_reply_to":"7f96bb07_2d2de445","updated":"2018-01-22 19:16:56.000000000","message":"Remembered why I didn\u0027t first time around - dict ordering makes the tests unreliable. I figured neither sorting in the patcher nor reimplementing the logic in the test were good solutions so stuck with one.","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"99cb6b2dc1d9df53fb0d373d6af51c51e5419ff6","unresolved":false,"context_lines":[{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_generic_get_deploy_patch_traits(self):"},{"line_number":119,"context_line":"        node \u003d ironic_utils.get_test_node(driver\u003d\u0027fake\u0027)"},{"line_number":120,"context_line":"        self.flavor[\u0027extra_specs\u0027][\u0027trait:CUSTOM_FOO\u0027] \u003d \u0027required\u0027"},{"line_number":121,"context_line":"        expected \u003d [{\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":122,"context_line":"                     \u0027value\u0027: \u0027[\"CUSTOM_FOO\"]\u0027,"},{"line_number":123,"context_line":"                     \u0027op\u0027: \u0027add\u0027}]"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_2d2de445","line":120,"in_reply_to":"7f96bb07_39a9b1de","updated":"2018-01-22 15:53:02.000000000","message":"Done","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"3c7d0f66f37a69c8be747c067d0dd6a8153e9919","unresolved":false,"context_lines":[{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_generic_get_deploy_patch_traits(self):"},{"line_number":119,"context_line":"        node \u003d ironic_utils.get_test_node(driver\u003d\u0027fake\u0027)"},{"line_number":120,"context_line":"        self.flavor[\u0027extra_specs\u0027][\u0027trait:CUSTOM_FOO\u0027] \u003d \u0027required\u0027"},{"line_number":121,"context_line":"        expected \u003d [{\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":122,"context_line":"                     \u0027value\u0027: \u0027[\"CUSTOM_FOO\"]\u0027,"},{"line_number":123,"context_line":"                     \u0027op\u0027: \u0027add\u0027}]"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_9fd21734","line":120,"in_reply_to":"7f96bb07_9f5f77e4","updated":"2018-01-22 19:18:31.000000000","message":"Okay, saw this after I posted my other comment.  So yeah, it\u0027s not frowned upon to sort in the source code for easier testing (and more predictable logging).","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"be37734e8715ec5bc09d12e1c1636915e31643d1","unresolved":false,"context_lines":[{"line_number":119,"context_line":"        node \u003d ironic_utils.get_test_node(driver\u003d\u0027fake\u0027)"},{"line_number":120,"context_line":"        self.flavor[\u0027extra_specs\u0027][\u0027trait:CUSTOM_FOO\u0027] \u003d \u0027required\u0027"},{"line_number":121,"context_line":"        expected \u003d [{\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":122,"context_line":"                     \u0027value\u0027: \u0027[\"CUSTOM_FOO\"]\u0027,"},{"line_number":123,"context_line":"                     \u0027op\u0027: \u0027add\u0027}]"},{"line_number":124,"context_line":"        expected +\u003d self._expected_deploy_patch"},{"line_number":125,"context_line":"        patch \u003d patcher.create(node).get_deploy_patch("}],"source_content_type":"text/x-python","patch_set":10,"id":"7f96bb07_1d728c4a","line":122,"updated":"2018-01-22 22:35:12.000000000","message":"Boo.  But okay.","commit_id":"761afd8c0733f7a3204e834afbca2e91dd49c0b7"},{"author":{"_account_id":782,"name":"John Garbutt","email":"john@johngarbutt.com","username":"johngarbutt"},"change_message_id":"9e6a5335cddbc246ffd0a272aaa183e72370ef84","unresolved":false,"context_lines":[{"line_number":119,"context_line":"        node \u003d ironic_utils.get_test_node(driver\u003d\u0027fake\u0027)"},{"line_number":120,"context_line":"        self.flavor[\u0027extra_specs\u0027][\u0027trait:CUSTOM_FOO\u0027] \u003d \u0027required\u0027"},{"line_number":121,"context_line":"        expected \u003d [{\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":122,"context_line":"                     \u0027value\u0027: \u0027[\"CUSTOM_FOO\"]\u0027,"},{"line_number":123,"context_line":"                     \u0027op\u0027: \u0027add\u0027}]"},{"line_number":124,"context_line":"        expected +\u003d self._expected_deploy_patch"},{"line_number":125,"context_line":"        patch \u003d patcher.create(node).get_deploy_patch("}],"source_content_type":"text/x-python","patch_set":10,"id":"7f96bb07_43ea34d8","line":122,"in_reply_to":"7f96bb07_1d728c4a","updated":"2018-01-24 12:44:06.000000000","message":"+1","commit_id":"761afd8c0733f7a3204e834afbca2e91dd49c0b7"}],"nova/virt/ironic/patcher.py":[{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"51920455be14abb1531c872a85e33db4be5cf181","unresolved":false,"context_lines":[{"line_number":94,"context_line":"        capabilities \u003d {}"},{"line_number":95,"context_line":"        traits \u003d {}"},{"line_number":96,"context_line":"        for key, val in extra_specs.items():"},{"line_number":97,"context_line":"            if key.startswith(\u0027capabilities:\u0027) or key.startswith(\u0027trait:\u0027):"},{"line_number":98,"context_line":"                # get the actual key."},{"line_number":99,"context_line":"                suffix, parsed_key \u003d key.split(\u0027:\u0027, 1)"},{"line_number":100,"context_line":"                if suffix \u003d\u003d \"capabilities\":"}],"source_content_type":"text/x-python","patch_set":2,"id":"3f4b6375_fbe401e2","line":97,"range":{"start_line":97,"start_character":66,"end_line":97,"end_character":71},"updated":"2017-10-24 13:13:16.000000000","message":"This will eventually need to take numbered groupings into account [1].  It may be adequate to take the colon off.  Or do a pattern match.\n\n[1] https://specs.openstack.org/openstack/nova-specs/specs/queens/approved/granular-resource-requests.html#syntax-in-flavors","commit_id":"7a9baedf9311c15cddcd866616d2157b85e64c51"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"df08305925e029f423bd17d8e79183c2b8d97c36","unresolved":false,"context_lines":[{"line_number":98,"context_line":"            # request syntax, where the \u0027trait\u0027 prefix may be followed by a"},{"line_number":99,"context_line":"            # numeric suffix: trait$N. For ironic we do not care about the"},{"line_number":100,"context_line":"            # group number."},{"line_number":101,"context_line":"            if key.startswith(\u0027capabilities:\u0027) or key.startswith(\u0027trait\u0027):"},{"line_number":102,"context_line":"                # get the actual key."},{"line_number":103,"context_line":"                prefix, parsed_key \u003d key.split(\u0027:\u0027, 1)"},{"line_number":104,"context_line":"                if prefix \u003d\u003d \"capabilities\":"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_6cf25572","line":101,"range":{"start_line":101,"start_character":50,"end_line":101,"end_character":74},"updated":"2018-01-18 21:31:06.000000000","message":"Oh, just this bit?  Okay.  So not actually any of the nova code that deals with the granular syntax.","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":782,"name":"John Garbutt","email":"john@johngarbutt.com","username":"johngarbutt"},"change_message_id":"268bd5e691ca4ec28b507dd0d98ba2ad8a08084b","unresolved":false,"context_lines":[{"line_number":98,"context_line":"            # request syntax, where the \u0027trait\u0027 prefix may be followed by a"},{"line_number":99,"context_line":"            # numeric suffix: trait$N. For ironic we do not care about the"},{"line_number":100,"context_line":"            # group number."},{"line_number":101,"context_line":"            if key.startswith(\u0027capabilities:\u0027) or key.startswith(\u0027trait\u0027):"},{"line_number":102,"context_line":"                # get the actual key."},{"line_number":103,"context_line":"                prefix, parsed_key \u003d key.split(\u0027:\u0027, 1)"},{"line_number":104,"context_line":"                if prefix \u003d\u003d \"capabilities\":"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_2404247b","line":101,"range":{"start_line":101,"start_character":50,"end_line":101,"end_character":74},"in_reply_to":"7f96bb07_61181084","updated":"2018-01-22 11:19:58.000000000","message":"Part of me feels bad that this is separate, but its consistent with the capabilities stuff, so I am OK with that.","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"d4b07dd70482211432b7441f7d296a665e30ca41","unresolved":false,"context_lines":[{"line_number":98,"context_line":"            # request syntax, where the \u0027trait\u0027 prefix may be followed by a"},{"line_number":99,"context_line":"            # numeric suffix: trait$N. For ironic we do not care about the"},{"line_number":100,"context_line":"            # group number."},{"line_number":101,"context_line":"            if key.startswith(\u0027capabilities:\u0027) or key.startswith(\u0027trait\u0027):"},{"line_number":102,"context_line":"                # get the actual key."},{"line_number":103,"context_line":"                prefix, parsed_key \u003d key.split(\u0027:\u0027, 1)"},{"line_number":104,"context_line":"                if prefix \u003d\u003d \"capabilities\":"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_61181084","line":101,"range":{"start_line":101,"start_character":50,"end_line":101,"end_character":74},"in_reply_to":"7f96bb07_6cf25572","updated":"2018-01-19 08:54:06.000000000","message":"Correct, just this.","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":782,"name":"John Garbutt","email":"john@johngarbutt.com","username":"johngarbutt"},"change_message_id":"268bd5e691ca4ec28b507dd0d98ba2ad8a08084b","unresolved":false,"context_lines":[{"line_number":104,"context_line":"                if prefix \u003d\u003d \"capabilities\":"},{"line_number":105,"context_line":"                    capabilities[parsed_key] \u003d val"},{"line_number":106,"context_line":"                else:"},{"line_number":107,"context_line":"                    # NOTE(mgoddard): Currently, the value must be \u0027required\u0027."},{"line_number":108,"context_line":"                    # We do not need to pass the value to ironic. When the"},{"line_number":109,"context_line":"                    # value can be something other than \u0027required\u0027, we may need"},{"line_number":110,"context_line":"                    # to filter out traits not supported by the node."}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_c4f8e07d","line":107,"updated":"2018-01-22 11:19:58.000000000","message":"Nit: very tempted to look at checking the value here.","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14826,"name":"Mark Goddard","email":"markgoddard86@gmail.com","username":"mgoddard"},"change_message_id":"99cb6b2dc1d9df53fb0d373d6af51c51e5419ff6","unresolved":false,"context_lines":[{"line_number":104,"context_line":"                if prefix \u003d\u003d \"capabilities\":"},{"line_number":105,"context_line":"                    capabilities[parsed_key] \u003d val"},{"line_number":106,"context_line":"                else:"},{"line_number":107,"context_line":"                    # NOTE(mgoddard): Currently, the value must be \u0027required\u0027."},{"line_number":108,"context_line":"                    # We do not need to pass the value to ironic. When the"},{"line_number":109,"context_line":"                    # value can be something other than \u0027required\u0027, we may need"},{"line_number":110,"context_line":"                    # to filter out traits not supported by the node."}],"source_content_type":"text/x-python","patch_set":8,"id":"7f96bb07_ed337c61","line":107,"in_reply_to":"7f96bb07_c4f8e07d","updated":"2018-01-22 15:53:02.000000000","message":"Hadn\u0027t realised how free-form the flavor properties are. Done","commit_id":"b7121014ca076f3469048cab15a029f5ffd68139"},{"author":{"_account_id":14070,"name":"Eric Fried","email":"openstack@fried.cc","username":"efried"},"change_message_id":"1f82846ab30b9bb3f47b02ebbb6c083d8995b50e","unresolved":false,"context_lines":[{"line_number":116,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(capabilities)})"},{"line_number":117,"context_line":"        if traits:"},{"line_number":118,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":119,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(traits)})"},{"line_number":120,"context_line":"        return patch"}],"source_content_type":"text/x-python","patch_set":9,"id":"7f96bb07_9f0457c8","line":119,"range":{"start_line":119,"start_character":48,"end_line":119,"end_character":71},"updated":"2018-01-22 19:17:18.000000000","message":"Consider sorting this, for testability.  The subsequent patch has a failure in py35 with these out of order.  So either sort here, or you\u0027ll have to do a much more complex \"equality\" check in the test case.","commit_id":"bb1ad976c99c228954caca291fe97c6cdb012f1f"},{"author":{"_account_id":6873,"name":"Matt Riedemann","email":"mriedem.os@gmail.com","username":"mriedem"},"change_message_id":"cfa488605bc4d0e98c30991548852fc98cf0830e","unresolved":false,"context_lines":[{"line_number":115,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/capabilities\u0027,"},{"line_number":116,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(capabilities)})"},{"line_number":117,"context_line":"        if traits:"},{"line_number":118,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":119,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(traits)})"},{"line_number":120,"context_line":"        return patch"}],"source_content_type":"text/x-python","patch_set":10,"id":"5f93b717_bee60ce8","line":118,"updated":"2018-01-25 19:34:44.000000000","message":"Why don\u0027t we have to bump the minimum required version of ironic API to 1.37 which added traits support?\n\nhttps://github.com/openstack/nova/blob/master/nova/virt/ironic/client_wrapper.py#L35","commit_id":"761afd8c0733f7a3204e834afbca2e91dd49c0b7"},{"author":{"_account_id":6873,"name":"Matt Riedemann","email":"mriedem.os@gmail.com","username":"mriedem"},"change_message_id":"f72572e43894806b3993951385deb2512ea48400","unresolved":false,"context_lines":[{"line_number":115,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/capabilities\u0027,"},{"line_number":116,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(capabilities)})"},{"line_number":117,"context_line":"        if traits:"},{"line_number":118,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":119,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(traits)})"},{"line_number":120,"context_line":"        return patch"}],"source_content_type":"text/x-python","patch_set":10,"id":"5f93b717_9e0a3024","line":118,"in_reply_to":"5f93b717_1e670050","updated":"2018-01-25 19:50:14.000000000","message":"Also, I should have read the spec more carefully:\n\nhttps://specs.openstack.org/openstack/nova-specs/specs/queens/approved/ironic-driver-traits.html#proposed-change\n\n\"we will also set the traits related flavor extra specs on a path /instance_info/traits. Note, this requires no API changes on the Ironic side, the path is a PATCH requests JSONPath change identifier.\"","commit_id":"761afd8c0733f7a3204e834afbca2e91dd49c0b7"},{"author":{"_account_id":6618,"name":"Ruby Loo","email":"opensrloo@gmail.com","username":"rloo"},"change_message_id":"510af3cec1996825b46eda68f699dcfa29624d6b","unresolved":false,"context_lines":[{"line_number":115,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/capabilities\u0027,"},{"line_number":116,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(capabilities)})"},{"line_number":117,"context_line":"        if traits:"},{"line_number":118,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":119,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(traits)})"},{"line_number":120,"context_line":"        return patch"}],"source_content_type":"text/x-python","patch_set":10,"id":"5f93b717_de3f58d4","line":118,"in_reply_to":"5f93b717_1e670050","updated":"2018-01-25 19:52:24.000000000","message":"Yup, Jim is right.","commit_id":"761afd8c0733f7a3204e834afbca2e91dd49c0b7"},{"author":{"_account_id":10343,"name":"Jim Rollenhagen","email":"jim@jimrollenhagen.com","username":"jimrollenhagen"},"change_message_id":"386dc21eb3c4b1ecea95e6db62b56532b54b7035","unresolved":false,"context_lines":[{"line_number":115,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/capabilities\u0027,"},{"line_number":116,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(capabilities)})"},{"line_number":117,"context_line":"        if traits:"},{"line_number":118,"context_line":"            patch.append({\u0027path\u0027: \u0027/instance_info/traits\u0027,"},{"line_number":119,"context_line":"                          \u0027op\u0027: \u0027add\u0027, \u0027value\u0027: jsonutils.dumps(traits)})"},{"line_number":120,"context_line":"        return patch"}],"source_content_type":"text/x-python","patch_set":10,"id":"5f93b717_1e670050","line":118,"in_reply_to":"5f93b717_bee60ce8","updated":"2018-01-25 19:38:30.000000000","message":"1.37 adds support for adding traits to nodes. However, instance_info is older, and is the mechanism which we use to send flavor-related information about this particular instance over to ironic.\n\nThis info will eventually be used to configure things about the node based on the traits in the flavor.","commit_id":"761afd8c0733f7a3204e834afbca2e91dd49c0b7"}]}
