diff --git a/module_build_service/models.py b/module_build_service/models.py index dc0e38a2..19b82682 100644 --- a/module_build_service/models.py +++ b/module_build_service/models.py @@ -38,10 +38,12 @@ from module_build_service import db, log, get_url_for, app, conf import module_build_service.messaging from sqlalchemy.orm import lazyload +from sqlalchemy import func, and_ import gi gi.require_version('Modulemd', '1.0') # noqa from gi.repository import Modulemd + # Just like koji.BUILD_STATES, except our own codes for modules. BUILD_STATES = { # This is (obviously) the first state a module build enters. @@ -241,6 +243,42 @@ class ModuleBuild(MBSBase): if component.batch <= self.batch ] + @staticmethod + def get_last_build_in_all_streams(session, name): + """ + Returns list of all last ModuleBuilds in "ready" state for all + streams for given module `name`. + """ + subq = session.query( + func.max(ModuleBuild.id).label('id') + ).group_by(ModuleBuild.name, ModuleBuild.stream).filter_by( + name=name, state=BUILD_STATES["ready"]).subquery('t2') + query = session.query(ModuleBuild).join( + subq, and_(ModuleBuild.id == subq.c.id)) + return query.all() + + @staticmethod + def get_last_build_in_stream(session, name, stream): + """ + Returns the last build in "ready" state for given name:stream. + """ + query = session.query(ModuleBuild) + query = query.filter_by(name=name, stream=stream, + state=BUILD_STATES["ready"]) + query = query.order_by(ModuleBuild.id.desc()) + return query.first() + + @staticmethod + def get_builds_in_version(session, name, stream, version): + """ + Returns list of all module builds in "ready" state for given + name:stream:version - it means all the contexts of this module. + """ + query = session.query(ModuleBuild) + query = query.filter_by(name=name, stream=stream, version=version, + state=BUILD_STATES["ready"]) + return query.all() + def mmd(self): try: mmd = Modulemd.Module().new_from_string(self.modulemd) diff --git a/tests/__init__.py b/tests/__init__.py index fadcbc7f..4bf53866 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -24,6 +24,7 @@ import os from datetime import datetime, timedelta from mock import patch import time +import hashlib from traceback import extract_stack import gi @@ -92,75 +93,86 @@ def clean_database(): db.create_all() -def init_data(data_size=10): +def init_data(data_size=10, contexts=False): """ Creates data_size * 3 modules in database in different states and with different component builds. See _populate_data for more info. + + :param bool contexts: If True, multiple streams and contexts in each stream + are generated for 'nginx' module. """ clean_database() with make_session(conf) as session: - _populate_data(session, data_size) + _populate_data(session, data_size, contexts=contexts) -def _populate_data(session, data_size=10): +def _populate_data(session, data_size=10, contexts=False): + num_contexts = 2 if contexts else 1 for index in range(data_size): - build_one = ModuleBuild() - build_one.name = 'nginx' - build_one.stream = '1' - build_one.version = 2 - build_one.state = BUILD_STATES['done'] - with open(os.path.join(base_dir, "staged_data", "nginx_mmd.yaml")) as mmd: - build_one.modulemd = mmd.read() - build_one.koji_tag = 'module-nginx-1.2' - build_one.scmurl = ('git://pkgs.domain.local/modules/nginx?' - '#ba95886c7a443b36a9ce31abda1f9bef22f2f8c9') - build_one.batch = 2 - # https://www.youtube.com/watch?v=iQGwrK_yDEg - build_one.owner = 'Moe Szyslak' - build_one.time_submitted = \ - datetime(2016, 9, 3, 11, 23, 20) + timedelta(minutes=(index * 10)) - build_one.time_modified = \ - datetime(2016, 9, 3, 11, 25, 32) + timedelta(minutes=(index * 10)) - build_one.time_completed = \ - datetime(2016, 9, 3, 11, 25, 32) + timedelta(minutes=(index * 10)) - build_one.rebuild_strategy = 'changed-and-after' - session.add(build_one) - session.commit() - build_one_component_release = get_rpm_release(build_one) + for context in range(num_contexts): + build_one = ModuleBuild() + build_one.name = 'nginx' + build_one.stream = '1' + build_one.version = 2 + index + build_one.state = BUILD_STATES['ready'] + if contexts: + build_one.stream = str(index) + unique_hash = hashlib.sha1("%s:%s:%d:%d" % ( + build_one.name, build_one.stream, build_one.version, context)).hexdigest() + build_one.build_context = unique_hash + build_one.runtime_context = unique_hash + with open(os.path.join(base_dir, "staged_data", "nginx_mmd.yaml")) as mmd: + build_one.modulemd = mmd.read() + build_one.koji_tag = 'module-nginx-1.2' + build_one.scmurl = ('git://pkgs.domain.local/modules/nginx?' + '#ba95886c7a443b36a9ce31abda1f9bef22f2f8c9') + build_one.batch = 2 + # https://www.youtube.com/watch?v=iQGwrK_yDEg + build_one.owner = 'Moe Szyslak' + build_one.time_submitted = \ + datetime(2016, 9, 3, 11, 23, 20) + timedelta(minutes=(index * 10)) + build_one.time_modified = \ + datetime(2016, 9, 3, 11, 25, 32) + timedelta(minutes=(index * 10)) + build_one.time_completed = \ + datetime(2016, 9, 3, 11, 25, 32) + timedelta(minutes=(index * 10)) + build_one.rebuild_strategy = 'changed-and-after' + session.add(build_one) + session.commit() + build_one_component_release = get_rpm_release(build_one) - component_one_build_one = ComponentBuild() - component_one_build_one.package = 'nginx' - component_one_build_one.scmurl = \ - ('git://pkgs.domain.local/rpms/nginx?' - '#ga95886c8a443b36a9ce31abda1f9bed22f2f8c3') - component_one_build_one.format = 'rpms' - component_one_build_one.task_id = 12312345 + index - component_one_build_one.state = koji.BUILD_STATES['COMPLETE'] - component_one_build_one.nvr = 'nginx-1.10.1-2.{0}'.format(build_one_component_release) - component_one_build_one.batch = 1 - component_one_build_one.module_id = 1 + index * 3 - component_one_build_one.tagged = True - component_one_build_one.tagged_in_final = True + component_one_build_one = ComponentBuild() + component_one_build_one.package = 'nginx' + component_one_build_one.scmurl = \ + ('git://pkgs.domain.local/rpms/nginx?' + '#ga95886c8a443b36a9ce31abda1f9bed22f2f8c3') + component_one_build_one.format = 'rpms' + component_one_build_one.task_id = 12312345 + index + component_one_build_one.state = koji.BUILD_STATES['COMPLETE'] + component_one_build_one.nvr = 'nginx-1.10.1-2.{0}'.format(build_one_component_release) + component_one_build_one.batch = 1 + component_one_build_one.module_id = 1 + index * 3 + component_one_build_one.tagged = True + component_one_build_one.tagged_in_final = True - component_two_build_one = ComponentBuild() - component_two_build_one.package = 'module-build-macros' - component_two_build_one.scmurl = \ - ('/tmp/module_build_service-build-macrosWZUPeK/SRPMS/' - 'module-build-macros-0.1-1.module_nginx_1_2.src.rpm') - component_two_build_one.format = 'rpms' - component_two_build_one.task_id = 12312321 + index - component_two_build_one.state = koji.BUILD_STATES['COMPLETE'] - component_two_build_one.nvr = \ - 'module-build-macros-01-1.{0}'.format(build_one_component_release) - component_two_build_one.batch = 2 - component_two_build_one.module_id = 1 + index * 3 - component_two_build_one.tagged = True - component_two_build_one.tagged_in_final = True + component_two_build_one = ComponentBuild() + component_two_build_one.package = 'module-build-macros' + component_two_build_one.scmurl = \ + ('/tmp/module_build_service-build-macrosWZUPeK/SRPMS/' + 'module-build-macros-0.1-1.module_nginx_1_2.src.rpm') + component_two_build_one.format = 'rpms' + component_two_build_one.task_id = 12312321 + index + component_two_build_one.state = koji.BUILD_STATES['COMPLETE'] + component_two_build_one.nvr = \ + 'module-build-macros-01-1.{0}'.format(build_one_component_release) + component_two_build_one.batch = 2 + component_two_build_one.module_id = 1 + index * 3 + component_two_build_one.tagged = True + component_two_build_one.tagged_in_final = True build_two = ModuleBuild() build_two.name = 'postgressql' build_two.stream = '1' - build_two.version = 2 + build_two.version = 2 + index build_two.state = BUILD_STATES['done'] build_two.modulemd = '' # Skipping since no tests rely on it build_two.koji_tag = 'module-postgressql-1.2' @@ -211,7 +223,7 @@ def _populate_data(session, data_size=10): build_three = ModuleBuild() build_three.name = 'testmodule' build_three.stream = '4.3.43' - build_three.version = 6 + build_three.version = 6 + index build_three.state = BUILD_STATES['wait'] build_three.modulemd = '' # Skipping because no tests rely on it build_three.koji_tag = None diff --git a/tests/test_models/test_models.py b/tests/test_models/test_models.py index c5bd6672..a9290607 100644 --- a/tests/test_models/test_models.py +++ b/tests/test_models/test_models.py @@ -27,6 +27,7 @@ gi.require_version('Modulemd', '1.0') # noqa from gi.repository import Modulemd from tests.test_models import init_data +from tests import init_data as init_data_contexts from module_build_service import conf from module_build_service.models import ComponentBuild, ModuleBuild, make_session @@ -74,3 +75,30 @@ class TestModels: assert build.build_context == 'f6e2aeec7576196241b9afa0b6b22acf2b6873d7' assert build.runtime_context == '1739827b08388842fc90ccc0b6070c59b7d856fc' assert build.context == 'e7a3d35e' + +class TestModelsGetStreamsContexts: + def setup_method(self, test_method): + init_data_contexts(contexts=True) + + def test_get_last_build_in_all_streams(self): + with make_session(conf) as session: + builds = ModuleBuild.get_last_build_in_all_streams( + session, "nginx") + builds = ["%s:%s:%s" % (build.name, build.stream, str(build.version)) + for build in builds] + assert builds == ["nginx:%d:%d" % (i, i + 2) for i in range(10)] + + def test_get_last_build_in_stream(self): + with make_session(conf) as session: + build = ModuleBuild.get_last_build_in_stream( + session, "nginx", "1") + build = "%s:%s:%s" % (build.name, build.stream, str(build.version)) + assert build == 'nginx:1:3' + + def test_get_builds_in_version(self): + with make_session(conf) as session: + builds = ModuleBuild.get_builds_in_version( + session, "nginx", "1", "3") + builds = ["%s:%s:%s:%s" % (build.name, build.stream, str(build.version), + build.context) for build in builds] + assert builds == ['nginx:1:3:d5a6c0fa', 'nginx:1:3:795e97c1'] diff --git a/tests/test_views/test_views.py b/tests/test_views/test_views.py index 5e962dd0..4ef977d8 100644 --- a/tests/test_views/test_views.py +++ b/tests/test_views/test_views.py @@ -124,7 +124,7 @@ class TestViews: assert data['name'] == 'nginx' assert data['owner'] == 'Moe Szyslak' assert data['stream'] == '1' - assert data['state'] == 3 + assert data['state'] == 5 assert data['state_reason'] is None assert data['tasks'] == { 'rpms': { @@ -154,8 +154,8 @@ class TestViews: assert data['id'] == 1 assert data['context'] == '00000000' assert data['name'] == 'nginx' - assert data['state'] == 3 - assert data['state_name'] == 'done' + assert data['state'] == 5 + assert data['state_name'] == 'ready' assert data['stream'] == '1' assert data['version'] == '2' @@ -174,8 +174,8 @@ class TestViews: assert data['owner'] == 'Moe Szyslak' assert data['scmurl'] == ('git://pkgs.domain.local/modules/nginx' '?#ba95886c7a443b36a9ce31abda1f9bef22f2f8c9') - assert data['state'] == 3 - assert data['state_name'] == 'done' + assert data['state'] == 5 + assert data['state_name'] == 'ready' assert data['state_reason'] is None # State trace is empty because we directly created these builds and didn't have them # transition, which creates these entries @@ -240,18 +240,18 @@ class TestViews: "state": 1, "state_reason": None, "task_id": 47383994, - "nvr": "module-build-macros-01-1.module+6+8d3cee59" + "nvr": "module-build-macros-01-1.module+6+f95651e2" }, "rubygem-rails": { "state": 3, "state_reason": None, "task_id": 2433434, - "nvr": "postgresql-9.5.3-4.module+6+8d3cee59" + "nvr": "postgresql-9.5.3-4.module+6+f95651e2" } } }, "owner": "some_other_user", - "version": "6", + "version": "7", "state_reason": None, "state": 1, "stream": "4.3.43", @@ -274,18 +274,18 @@ class TestViews: "state": 1, "state_reason": None, "task_id": 47383994, - "nvr": "module-build-macros-01-1.module+5+0557c87d" + "nvr": "module-build-macros-01-1.module+5+fa947d31" }, "postgresql": { "state": 1, "state_reason": None, "task_id": 2433434, - "nvr": "postgresql-9.5.3-4.module+5+0557c87d" + "nvr": "postgresql-9.5.3-4.module+5+fa947d31" } } }, "owner": "some_user", - "version": "2", + "version": "3", "state_reason": None, "state": 3, "stream": "1", @@ -300,6 +300,7 @@ class TestViews: "koji_tag": "module-postgressql-1.2" } ] + assert items == expected def test_query_builds_with_id_error(self): @@ -439,7 +440,7 @@ class TestViews: rv = self.client.get( '/module-build-service/1/module-builds/?state=3') data = json.loads(rv.data) - assert data['meta']['total'] == 4 + assert data['meta']['total'] == 2 def test_query_builds_two_filters(self): rv = self.client.get('/module-build-service/1/module-builds/?owner=Moe%20Szyslak' @@ -451,12 +452,11 @@ class TestViews: rv = self.client.get( '/module-build-service/1/module-builds/?name=postgressql&stream=1&version=2') data = json.loads(rv.data) - # TODO: The nsv should really be unique in the test data for item in data['items']: assert item['name'] == 'postgressql' assert item['stream'] == '1' assert item['version'] == '2' - assert data['meta']['total'] == 2 + assert data['meta']['total'] == 1 def test_query_builds_filter_invalid_date(self): rv = self.client.get(