)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":21813,"name":"Andrey Volkov","email":"m@amadev.ru","username":"avolkov"},"change_message_id":"a683009c17b265146a0f36506196a456cb4d5daa","unresolved":false,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"Aggregate create and destroy work against API db"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Make aggregate.create() and destroy() use the API rather"},{"line_number":10,"context_line":"than cell database."},{"line_number":11,"context_line":""},{"line_number":12,"context_line":"blueprint cells-aggregate-api-db"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":11,"id":"7aa08908_1c1525e4","line":9,"updated":"2016-06-08 12:42:03.000000000","message":"API database","commit_id":"c48cc213a51f8c90f23191cee63920430094608c"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"0175fc6610610fbe87d2c8ca6295f76875e402c0","unresolved":false,"context_lines":[{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Make aggregate.create() and destroy() use the API rather than cell database."},{"line_number":10,"context_line":"Also block aggregate creation until main database empty. This makes"},{"line_number":11,"context_line":"Aggregate.create() fail until the main database has had all of its aggreagtes"},{"line_number":12,"context_line":"migrated. Since we want to avoid any overlap or clashes in integer ids we"},{"line_number":13,"context_line":"need to enforce this."},{"line_number":14,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":26,"id":"bacf61ea_131db266","line":11,"updated":"2016-08-03 00:04:16.000000000","message":"s/aggreagtes/aggregates/","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"218daed20ca6b2ce80285a972e006008fcb4152c","unresolved":false,"context_lines":[{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Make aggregate.create() and destroy() use the API rather than cell database."},{"line_number":10,"context_line":"Also block aggregate creation until main database empty. This makes"},{"line_number":11,"context_line":"Aggregate.create() fail until the main database has had all of its aggreagtes"},{"line_number":12,"context_line":"migrated. Since we want to avoid any overlap or clashes in integer ids we"},{"line_number":13,"context_line":"need to enforce this."},{"line_number":14,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":26,"id":"bacf61ea_9843816c","line":11,"updated":"2016-08-03 00:01:16.000000000","message":"s/aggreagtes/aggregates/","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"}],"nova/db/sqlalchemy/api_models.py":[{"author":{"_account_id":5441,"name":"Andrew Laski","email":"andrew@lascii.com","username":"alaski"},"change_message_id":"2be586f091c3f8831e78c056927964eb422e9c52","unresolved":false,"context_lines":[{"line_number":75,"context_line":"    name \u003d Column(String(255))"},{"line_number":76,"context_line":"    _hosts \u003d orm.relationship(AggregateHost,"},{"line_number":77,"context_line":"                    primaryjoin\u003d\u0027Aggregate.id \u003d\u003d AggregateHost.aggregate_id\u0027,"},{"line_number":78,"context_line":"                    cascade\u003d\u0027delete\u0027)"},{"line_number":79,"context_line":"    _metadata \u003d orm.relationship(AggregateMetadata,"},{"line_number":80,"context_line":"                primaryjoin\u003d\u0027Aggregate.id \u003d\u003d AggregateMetadata.aggregate_id\u0027,"},{"line_number":81,"context_line":"                cascade\u003d\u0027delete\u0027)"}],"source_content_type":"text/x-python","patch_set":25,"id":"bacf61ea_766275d9","line":78,"updated":"2016-08-01 14:57:16.000000000","message":"Fancy. It\u0027s about time we started being better about cleaning up relationships.","commit_id":"0f3c0f2cfda21aa8603274ec6a54ce3c123bfd77"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"0175fc6610610fbe87d2c8ca6295f76875e402c0","unresolved":false,"context_lines":[{"line_number":78,"context_line":"                    cascade\u003d\u0027delete\u0027)"},{"line_number":79,"context_line":"    _metadata \u003d orm.relationship(AggregateMetadata,"},{"line_number":80,"context_line":"                primaryjoin\u003d\u0027Aggregate.id \u003d\u003d AggregateMetadata.aggregate_id\u0027,"},{"line_number":81,"context_line":"                cascade\u003d\u0027delete\u0027)"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"    @property"},{"line_number":84,"context_line":"    def _extra_keys(self):"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_332a767f","line":81,"updated":"2016-08-03 00:04:16.000000000","message":"Sure, this is fine. This doesn\u0027t affect the database schema on the server side, though, just FYI... what it will do, however, is issue DELETE SQL statements for associated metadata and hosts when the parent aggregate record is deleted. It does this via SQLAlchemy, though, not the database itself.\n\nAlso note that cascade\u003d\u0027delete\u0027 on a relationship() like the above will only work for deletes that are done using session.delete(). If you delete using, say, session.query().filter().delete(), like you do below, the above cascade\u003d\u0027delete\u0027 has no effect at all. :) My suggestion would be to just remove the cascade\u003d\u0027delete\u0027 entirely and always ensure that metadata and hosts are deleted properly before the parent record.","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"218daed20ca6b2ce80285a972e006008fcb4152c","unresolved":false,"context_lines":[{"line_number":78,"context_line":"                    cascade\u003d\u0027delete\u0027)"},{"line_number":79,"context_line":"    _metadata \u003d orm.relationship(AggregateMetadata,"},{"line_number":80,"context_line":"                primaryjoin\u003d\u0027Aggregate.id \u003d\u003d AggregateMetadata.aggregate_id\u0027,"},{"line_number":81,"context_line":"                cascade\u003d\u0027delete\u0027)"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"    @property"},{"line_number":84,"context_line":"    def _extra_keys(self):"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_b85ac54e","line":81,"updated":"2016-08-03 00:01:16.000000000","message":"Sure, this is fine. This doesn\u0027t affect the database schema on the server side, though, just FYI... what it will do, however, is issue DELETE SQL statements for associated metadata and hosts when the parent aggregate record is deleted. It does this via SQLAlchemy, though, not the database itself.\n\nAlso note that cascade\u003d\u0027delete\u0027 on a relationship() like the above will only work for deletes that are done using session.delete(). If you delete using, say, session.query().filter().delete(), like you do below, the above cascade\u003d\u0027delete\u0027 has no effect at all. :) My suggestion would be to just remove the cascade\u003d\u0027delete\u0027 entirely and always ensure that metadata and hosts are deleted properly before the parent record.","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":6873,"name":"Matt Riedemann","email":"mriedem.os@gmail.com","username":"mriedem"},"change_message_id":"416acc47b75bd8abe157c888f5e8098f2939ff58","unresolved":false,"context_lines":[{"line_number":78,"context_line":"                    cascade\u003d\u0027delete\u0027)"},{"line_number":79,"context_line":"    _metadata \u003d orm.relationship(AggregateMetadata,"},{"line_number":80,"context_line":"                primaryjoin\u003d\u0027Aggregate.id \u003d\u003d AggregateMetadata.aggregate_id\u0027,"},{"line_number":81,"context_line":"                cascade\u003d\u0027delete\u0027)"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"    @property"},{"line_number":84,"context_line":"    def _extra_keys(self):"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_9325225e","line":81,"in_reply_to":"bacf61ea_332a767f","updated":"2016-08-03 00:20:38.000000000","message":"If these cascading deletes don\u0027t work for us then we should remove them in a follow up change to avoid confusion in a year or two from now.","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"}],"nova/objects/aggregate.py":[{"author":{"_account_id":21813,"name":"Andrey Volkov","email":"m@amadev.ru","username":"avolkov"},"change_message_id":"a683009c17b265146a0f36506196a456cb4d5daa","unresolved":false,"context_lines":[{"line_number":151,"context_line":"    query \u003d context.session.query(api_models.Aggregate)"},{"line_number":152,"context_line":"    query \u003d query.filter(api_models.Aggregate.name \u003d\u003d values[\u0027name\u0027])"},{"line_number":153,"context_line":"    aggregate \u003d query.first()"},{"line_number":154,"context_line":""},{"line_number":155,"context_line":"    if not aggregate:"},{"line_number":156,"context_line":"        aggregate \u003d api_models.Aggregate()"},{"line_number":157,"context_line":"        aggregate.update(values)"}],"source_content_type":"text/x-python","patch_set":11,"id":"7aa08908_7c854958","line":154,"updated":"2016-06-08 12:42:03.000000000","message":"I wonder it is okay to use db methods in nova.objects. I see nova.db.api and nova.db.sqlalchemy.api but in rare cases there are queries in objects. How do you choose?","commit_id":"c48cc213a51f8c90f23191cee63920430094608c"},{"author":{"_account_id":21813,"name":"Andrey Volkov","email":"m@amadev.ru","username":"avolkov"},"change_message_id":"a683009c17b265146a0f36506196a456cb4d5daa","unresolved":false,"context_lines":[{"line_number":152,"context_line":"    query \u003d query.filter(api_models.Aggregate.name \u003d\u003d values[\u0027name\u0027])"},{"line_number":153,"context_line":"    aggregate \u003d query.first()"},{"line_number":154,"context_line":""},{"line_number":155,"context_line":"    if not aggregate:"},{"line_number":156,"context_line":"        aggregate \u003d api_models.Aggregate()"},{"line_number":157,"context_line":"        aggregate.update(values)"},{"line_number":158,"context_line":"        aggregate.save(context.session)"}],"source_content_type":"text/x-python","patch_set":11,"id":"7aa08908_aadf5f38","line":155,"updated":"2016-06-08 12:42:03.000000000","message":"I think it\u0027s better to add unique constraints to aggregate.name because code has race possibility.","commit_id":"c48cc213a51f8c90f23191cee63920430094608c"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"8d9c2e2eb554234b55b2626daae07fa6adc5a94a","unresolved":false,"context_lines":[{"line_number":169,"context_line":"    if not aggregate:"},{"line_number":170,"context_line":"        aggregate \u003d api_models.Aggregate()"},{"line_number":171,"context_line":"        aggregate.update(values)"},{"line_number":172,"context_line":"        aggregate.save(context.session)"},{"line_number":173,"context_line":"        # We don\u0027t want these to be lazy loaded later.  We know there is"},{"line_number":174,"context_line":"        # nothing here since we just created this aggregate."},{"line_number":175,"context_line":"        aggregate._hosts \u003d []"}],"source_content_type":"text/x-python","patch_set":14,"id":"3aaa91ec_df9c9d5f","line":172,"updated":"2016-06-22 20:32:12.000000000","message":"OK, so right here, values param is *not* going to contain an \u0027id\u0027 key (because the id field is readonly since it\u0027s an autoincrement identifier in the cell DB). This means that the aggregate record in the API database for this aggregate will get an autoincrementing identifier that may be totally different from the autoincrementing identifier in the cell DB. This is why I believe we need to use UUID lookups when doing the \"is this thing in the cell DB or in the API DB?\" queries in previous patches...","commit_id":"8bea86cfda586a8876785f76813819f7df1db7ca"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"4327d76c9c97f24276b0c1a94b6abb4ef12fc7a3","unresolved":false,"context_lines":[{"line_number":179,"context_line":"    if metadata:"},{"line_number":180,"context_line":"        _metadata_add_to_db(context, aggregate.id, metadata)"},{"line_number":181,"context_line":"        context.session.expire(aggregate, [\u0027_metadata\u0027])"},{"line_number":182,"context_line":"        aggregate._metadata"},{"line_number":183,"context_line":""},{"line_number":184,"context_line":"    return aggregate"},{"line_number":185,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"bacf61ea_0163abb2","line":182,"updated":"2016-07-27 21:50:16.000000000","message":"OK, so the above re-lazyloads the metadata from the API database?","commit_id":"0af43f82910feafd95625bdc6f8cd172d3ca559f"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"4327d76c9c97f24276b0c1a94b6abb4ef12fc7a3","unresolved":false,"context_lines":[{"line_number":348,"context_line":"        if not self._ensure_migrated(self._context):"},{"line_number":349,"context_line":"            raise exception.ObjectActionError("},{"line_number":350,"context_line":"                action\u003d\u0027create\u0027,"},{"line_number":351,"context_line":"                reason\u003d\u0027main database still contains aggregates\u0027)"},{"line_number":352,"context_line":""},{"line_number":353,"context_line":"        self._assert_no_hosts(\u0027create\u0027)"},{"line_number":354,"context_line":"        updates \u003d self.obj_get_changes()"}],"source_content_type":"text/x-python","patch_set":22,"id":"bacf61ea_6158a7fb","line":351,"updated":"2016-07-27 21:50:16.000000000","message":"++","commit_id":"0af43f82910feafd95625bdc6f8cd172d3ca559f"},{"author":{"_account_id":5441,"name":"Andrew Laski","email":"andrew@lascii.com","username":"alaski"},"change_message_id":"2be586f091c3f8831e78c056927964eb422e9c52","unresolved":false,"context_lines":[{"line_number":343,"context_line":"                                              reason\u003d\u0027already created\u0027)"},{"line_number":344,"context_line":""},{"line_number":345,"context_line":"        # NOTE(mdoff): Once we have made it past a point where we know"},{"line_number":346,"context_line":"        # all flavors have been migrated, we can remove this. Ideally"},{"line_number":347,"context_line":"        # in Ocata with a blocker migration to be sure."},{"line_number":348,"context_line":"        if not self._ensure_migrated(self._context):"},{"line_number":349,"context_line":"            raise exception.ObjectActionError("}],"source_content_type":"text/x-python","patch_set":25,"id":"bacf61ea_56b49133","line":346,"range":{"start_line":346,"start_character":14,"end_line":346,"end_character":21},"updated":"2016-08-01 14:57:16.000000000","message":"copy/paste error","commit_id":"0f3c0f2cfda21aa8603274ec6a54ce3c123bfd77"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"218daed20ca6b2ce80285a972e006008fcb4152c","unresolved":false,"context_lines":[{"line_number":179,"context_line":"    if metadata:"},{"line_number":180,"context_line":"        _metadata_add_to_db(context, aggregate.id, metadata)"},{"line_number":181,"context_line":"        context.session.expire(aggregate, [\u0027_metadata\u0027])"},{"line_number":182,"context_line":"        aggregate._metadata"},{"line_number":183,"context_line":""},{"line_number":184,"context_line":"    return aggregate"},{"line_number":185,"context_line":""}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_18583156","line":182,"updated":"2016-08-03 00:01:16.000000000","message":"Heh, this is fine but isn\u0027t particularly explicit. Might want to add a code comment saying the above loads metadata model objects into the aggregate model object immediately after saving the same to the database. Alternately, you could just set aggregate._metadata to a list of constructed AggregateMetadata objects, but meh :)","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"0175fc6610610fbe87d2c8ca6295f76875e402c0","unresolved":false,"context_lines":[{"line_number":179,"context_line":"    if metadata:"},{"line_number":180,"context_line":"        _metadata_add_to_db(context, aggregate.id, metadata)"},{"line_number":181,"context_line":"        context.session.expire(aggregate, [\u0027_metadata\u0027])"},{"line_number":182,"context_line":"        aggregate._metadata"},{"line_number":183,"context_line":""},{"line_number":184,"context_line":"    return aggregate"},{"line_number":185,"context_line":""}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_931fe25a","line":182,"updated":"2016-08-03 00:04:16.000000000","message":"Heh, this is fine but isn\u0027t particularly explicit. Might want to add a code comment saying the above loads metadata model objects into the aggregate model object immediately after saving the same to the database. Alternately, you could just set aggregate._metadata to a list of constructed AggregateMetadata objects, but meh :)","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"0175fc6610610fbe87d2c8ca6295f76875e402c0","unresolved":false,"context_lines":[{"line_number":329,"context_line":"    @db_api.main_context_manager.reader"},{"line_number":330,"context_line":"    def _ensure_migrated(context):"},{"line_number":331,"context_line":"        result \u003d context.session.query(main_models.Aggregate).\\"},{"line_number":332,"context_line":"                 filter_by(deleted\u003d0).count()"},{"line_number":333,"context_line":"        if result:"},{"line_number":334,"context_line":"            LOG.warning("},{"line_number":335,"context_line":"                _LW(\u0027Main database contains %(count)i unmigrated aggregates\u0027),"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_7324eeae","line":332,"updated":"2016-08-03 00:04:16.000000000","message":"++","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"218daed20ca6b2ce80285a972e006008fcb4152c","unresolved":false,"context_lines":[{"line_number":329,"context_line":"    @db_api.main_context_manager.reader"},{"line_number":330,"context_line":"    def _ensure_migrated(context):"},{"line_number":331,"context_line":"        result \u003d context.session.query(main_models.Aggregate).\\"},{"line_number":332,"context_line":"                 filter_by(deleted\u003d0).count()"},{"line_number":333,"context_line":"        if result:"},{"line_number":334,"context_line":"            LOG.warning("},{"line_number":335,"context_line":"                _LW(\u0027Main database contains %(count)i unmigrated aggregates\u0027),"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_f8543d22","line":332,"updated":"2016-08-03 00:01:16.000000000","message":"++","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"}],"nova/tests/functional/db/test_aggregate.py":[{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"218daed20ca6b2ce80285a972e006008fcb4152c","unresolved":false,"context_lines":[{"line_number":472,"context_line":""},{"line_number":473,"context_line":"def create_aggregate(context, db_id, in_api\u003dTrue):"},{"line_number":474,"context_line":"    if in_api:"},{"line_number":475,"context_line":"        fake_aggregate \u003d _get_fake_aggregate(db_id, in_api\u003dFalse, result\u003dFalse)"},{"line_number":476,"context_line":"        aggregate_obj._aggregate_create_in_db(context, fake_aggregate,"},{"line_number":477,"context_line":"                                       metadata\u003d_get_fake_metadata(db_id))"},{"line_number":478,"context_line":"        for host in _get_fake_hosts(db_id):"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_78484d88","line":475,"updated":"2016-08-03 00:01:16.000000000","message":"For a future patch... these aren\u0027t fake aggregates by any means :) They are aggregate fixtures.","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"0175fc6610610fbe87d2c8ca6295f76875e402c0","unresolved":false,"context_lines":[{"line_number":472,"context_line":""},{"line_number":473,"context_line":"def create_aggregate(context, db_id, in_api\u003dTrue):"},{"line_number":474,"context_line":"    if in_api:"},{"line_number":475,"context_line":"        fake_aggregate \u003d _get_fake_aggregate(db_id, in_api\u003dFalse, result\u003dFalse)"},{"line_number":476,"context_line":"        aggregate_obj._aggregate_create_in_db(context, fake_aggregate,"},{"line_number":477,"context_line":"                                       metadata\u003d_get_fake_metadata(db_id))"},{"line_number":478,"context_line":"        for host in _get_fake_hosts(db_id):"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_f319fe7a","line":475,"updated":"2016-08-03 00:04:16.000000000","message":"For a future patch... these aren\u0027t fake aggregates by any means :) They are aggregate fixtures.","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"0175fc6610610fbe87d2c8ca6295f76875e402c0","unresolved":false,"context_lines":[{"line_number":592,"context_line":"        new_agg \u003d aggregate_obj.Aggregate(self.context)"},{"line_number":593,"context_line":"        new_agg.name \u003d \u0027new-aggregate\u0027"},{"line_number":594,"context_line":"        self.assertRaises(exception.ObjectActionError,"},{"line_number":595,"context_line":"                          new_agg.create)"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_53256aac","line":595,"updated":"2016-08-03 00:04:16.000000000","message":"Excellent functional object tests. ++","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"},{"author":{"_account_id":7,"name":"Jay Pipes","email":"jaypipes@gmail.com","username":"jaypipes"},"change_message_id":"218daed20ca6b2ce80285a972e006008fcb4152c","unresolved":false,"context_lines":[{"line_number":592,"context_line":"        new_agg \u003d aggregate_obj.Aggregate(self.context)"},{"line_number":593,"context_line":"        new_agg.name \u003d \u0027new-aggregate\u0027"},{"line_number":594,"context_line":"        self.assertRaises(exception.ObjectActionError,"},{"line_number":595,"context_line":"                          new_agg.create)"}],"source_content_type":"text/x-python","patch_set":26,"id":"bacf61ea_d85df949","line":595,"updated":"2016-08-03 00:01:16.000000000","message":"Excellent functional object tests. ++","commit_id":"7f82c5e6816b3763cde5aee8ba97c56184aeb2aa"}]}
