From 166636beedc92b9b78d8bad53f98603b572a0f30 Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Sep 19 2017 05:33:38 +0000 Subject: Just update time_to_expire when renew an expired compose Signed-off-by: Chenxiong Qi --- diff --git a/server/odcs/server/models.py b/server/odcs/server/models.py index 0be3724..2bb28de 100644 --- a/server/odcs/server/models.py +++ b/server/odcs/server/models.py @@ -263,3 +263,20 @@ class Compose(ODCSBase): return "" % ( self.source, self.source_type, INVERSE_COMPOSE_STATES[self.state], self.owner) + + def get_reused_compose(self): + """Get compose this compose reuses""" + return db.session.query(Compose).filter( + Compose.id == self.reused_id).first() + + def get_reusing_composes(self): + """Get composes that are reusing this compose""" + return db.session.query(Compose).filter( + Compose.reused_id == self.id).all() + + def extend_expiration(self, _from, seconds_to_live): + """Extend time to expire""" + new_expiration = max(self.time_to_expire, + _from + timedelta(seconds=seconds_to_live)) + if new_expiration != self.time_to_expire: + self.time_to_expire = new_expiration diff --git a/server/odcs/server/views.py b/server/odcs/server/views.py index a2b680a..0b5ea70 100644 --- a/server/odcs/server/views.py +++ b/server/odcs/server/views.py @@ -113,19 +113,37 @@ class ODCSAPI(MethodView): Compose.id == data["id"], Compose.state.in_( [COMPOSE_STATES["removed"], + COMPOSE_STATES["done"], COMPOSE_STATES["failed"]])).first() + if not old_compose: - err = "No expired or failed compose with id %s" % data["id"] + err = "No compose with id %s found" % data["id"] log.error(err) raise ValueError(err) - log.info("%r: Going to regenerate the compose", old_compose) + state = old_compose.state + if state in (COMPOSE_STATES['removed'], COMPOSE_STATES['failed']): + log.info("%r: Going to regenerate the compose", old_compose) + compose = Compose.create_copy(db.session, old_compose, owner, + seconds_to_live) + db.session.add(compose) + db.session.commit() + return jsonify(compose.json()), 200 - compose = Compose.create_copy(db.session, old_compose, owner, - seconds_to_live) - db.session.add(compose) + # Otherwise, just extend expiration to make it usable for longer time. + extend_from = datetime.datetime.utcnow() + old_compose.extend_expiration(extend_from, seconds_to_live) + log.info('Extended time_to_expire for compose %r to %s', + old_compose, old_compose.time_to_expire) + # As well as extending those composes that reuse this this compose, + # and the one this compose reuses. + reused_compose = old_compose.get_reused_compose() + if reused_compose: + reused_compose.extend_expiration(extend_from, seconds_to_live) + for c in old_compose.get_reusing_composes(): + c.extend_expiration(extend_from, seconds_to_live) db.session.commit() - return jsonify(compose.json()), 200 + return jsonify(old_compose.json()), 200 source_data = data.get('source', None) if not isinstance(source_data, dict): diff --git a/server/tests/test_models.py b/server/tests/test_models.py index 9a01c43..8dabc7a 100644 --- a/server/tests/test_models.py +++ b/server/tests/test_models.py @@ -21,6 +21,9 @@ # Written by Jan Kaluza # -*- coding: utf-8 -*- +from datetime import datetime +from datetime import timedelta + from odcs.server import db from odcs.server.models import Compose from odcs.server.types import COMPOSE_RESULTS @@ -81,3 +84,58 @@ class TestUserModel(ModelsBaseTest): user = User.find_user_by_name('tester1') self.assertEqual('tester1', user.username) + + +class ComposeModel(ModelsBaseTest): + """Test Compose Model""" + + def setUp(self): + super(ComposeModel, self).setUp() + + self.c1 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f26", + COMPOSE_RESULTS["repository"], 60) + self.c2 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f26", + COMPOSE_RESULTS["repository"], 60, packages='pkg1') + self.c3 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f26", + COMPOSE_RESULTS["repository"], 60, packages='pkg1') + self.c4 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f26", + COMPOSE_RESULTS["repository"], 60, packages='pkg1') + + map(db.session.add, (self.c1, self.c2, self.c3, self.c4)) + db.session.commit() + + self.c1.reused_id = self.c3.id + self.c2.reused_id = self.c3.id + self.c3.reused_id = self.c4.id + db.session.commit() + + def test_get_reused_compose(self): + compose = self.c3.get_reused_compose() + self.assertEqual(self.c4, compose) + + def test_get_reusing_composes(self): + composes = self.c3.get_reusing_composes() + self.assertEqual(2, len(composes)) + self.assertEqual([self.c1, self.c2], list(composes)) + + def test_extend_expiration(self): + from_now = datetime.utcnow() + seconds_to_live = 100 + self.c1.extend_expiration(from_now, seconds_to_live) + db.session.commit() + + expected_time_to_expire = from_now + timedelta(seconds=seconds_to_live) + self.assertEqual(expected_time_to_expire, self.c1.time_to_expire) + + def test_not_extend_expiration(self): + from_now = datetime.utcnow() + seconds_to_live = (self.c1.time_to_expire - datetime.utcnow()).seconds / 2 + + orig_time_to_expire = self.c1.time_to_expire + self.c1.extend_expiration(from_now, seconds_to_live) + + self.assertEqual(orig_time_to_expire, self.c1.time_to_expire) diff --git a/server/tests/test_views.py b/server/tests/test_views.py index 738457b..f0421e2 100644 --- a/server/tests/test_views.py +++ b/server/tests/test_views.py @@ -21,9 +21,10 @@ # Written by Jan Kaluza import contextlib -import datetime import json +from datetime import datetime, timedelta + import flask from freezegun import freeze_time @@ -43,11 +44,10 @@ def user_loader(username): return User.find_user_by_name(username=username) -class TestViews(ModelsBaseTest): - maxDiff = None +class ViewBaseTest(ModelsBaseTest): def setUp(self): - super(TestViews, self).setUp() + super(ViewBaseTest, self).setUp() patched_allowed_clients = {'groups': ['composer'], 'users': ['dev']} patched_admins = {'groups': ['admin'], 'users': ['root']} @@ -62,21 +62,10 @@ class TestViews(ModelsBaseTest): self.client = app.test_client() - self.initial_datetime = datetime.datetime(year=2016, month=1, day=1, - hour=0, minute=0, second=0) - with freeze_time(self.initial_datetime): - self.c1 = Compose.create( - db.session, "unknown", PungiSourceType.MODULE, "testmodule-master", - COMPOSE_RESULTS["repository"], 60) - self.c2 = Compose.create( - db.session, "me", PungiSourceType.KOJI_TAG, "f26", - COMPOSE_RESULTS["repository"], 60) - db.session.add(self.c1) - db.session.add(self.c2) - db.session.commit() + self.setup_test_data() def tearDown(self): - super(TestViews, self).tearDown() + super(ViewBaseTest, self).tearDown() self.patch_allowed_clients.stop() self.patch_admins.stop() @@ -112,6 +101,27 @@ class TestViews(ModelsBaseTest): if patch_auth_backend is not None: patch_auth_backend.stop() + def setup_test_data(self): + """Set up data for running tests""" + + +class TestViews(ViewBaseTest): + maxDiff = None + + def setup_test_data(self): + self.initial_datetime = datetime(year=2016, month=1, day=1, + hour=0, minute=0, second=0) + with freeze_time(self.initial_datetime): + self.c1 = Compose.create( + db.session, "unknown", PungiSourceType.MODULE, "testmodule-master", + COMPOSE_RESULTS["repository"], 60) + self.c2 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f26", + COMPOSE_RESULTS["repository"], 60) + db.session.add(self.c1) + db.session.add(self.c2) + db.session.commit() + def test_submit_build(self): with self.test_request_context(user='dev'): rv = self.client.post('/odcs/1/composes/', data=json.dumps( @@ -185,14 +195,14 @@ class TestViews(ModelsBaseTest): rv = self.client.post('/odcs/1/composes/', data=json.dumps({'id': 1})) data = json.loads(rv.data.decode('utf8')) - self.assertEqual(data['message'], 'No expired or failed compose with id 1') + self.assertEqual(data['message'], 'No compose with id 1 found') def test_submit_build_resurrection_not_found(self): with self.test_request_context(user='dev'): rv = self.client.post('/odcs/1/composes/', data=json.dumps({'id': 100})) data = json.loads(rv.data.decode('utf8')) - self.assertEqual(data['message'], 'No expired or failed compose with id 100') + self.assertEqual(data['message'], 'No compose with id 100 found') def test_query_compose(self): resp = self.client.get('/odcs/1/composes/1') @@ -357,9 +367,9 @@ class TestViews(ModelsBaseTest): {'source': {'type': 'module', 'source': 'testmodule-master'}, 'seconds-to-live': 60 * 60 * 12})) data = json.loads(rv.data.decode('utf8')) - time_submitted = datetime.datetime.strptime(data['time_submitted'], "%Y-%m-%dT%H:%M:%SZ") - time_to_expire = datetime.datetime.strptime(data['time_to_expire'], "%Y-%m-%dT%H:%M:%SZ") - delta = datetime.timedelta(hours=12) + time_submitted = datetime.strptime(data['time_submitted'], "%Y-%m-%dT%H:%M:%SZ") + time_to_expire = datetime.strptime(data['time_to_expire'], "%Y-%m-%dT%H:%M:%SZ") + delta = timedelta(hours=12) self.assertEqual(time_to_expire - time_submitted, delta) @patch.object(odcs.server.config.Config, 'max_seconds_to_live', new_callable=PropertyMock) @@ -375,9 +385,9 @@ class TestViews(ModelsBaseTest): {'source': {'type': 'module', 'source': 'testmodule-master'}, 'seconds-to-live': 60 * 60 * 24 * 7})) data = json.loads(rv.data.decode('utf8')) - time_submitted = datetime.datetime.strptime(data['time_submitted'], "%Y-%m-%dT%H:%M:%SZ") - time_to_expire = datetime.datetime.strptime(data['time_to_expire'], "%Y-%m-%dT%H:%M:%SZ") - delta = datetime.timedelta(days=3) + time_submitted = datetime.strptime(data['time_submitted'], "%Y-%m-%dT%H:%M:%SZ") + time_to_expire = datetime.strptime(data['time_to_expire'], "%Y-%m-%dT%H:%M:%SZ") + delta = timedelta(days=3) self.assertEqual(time_to_expire - time_submitted, delta) @patch.object(odcs.server.config.Config, 'max_seconds_to_live', new_callable=PropertyMock) @@ -392,9 +402,9 @@ class TestViews(ModelsBaseTest): {'source': {'type': 'module', 'source': 'testmodule-master'}})) data = json.loads(rv.data.decode('utf8')) - time_submitted = datetime.datetime.strptime(data['time_submitted'], "%Y-%m-%dT%H:%M:%SZ") - time_to_expire = datetime.datetime.strptime(data['time_to_expire'], "%Y-%m-%dT%H:%M:%SZ") - delta = datetime.timedelta(hours=24) + time_submitted = datetime.strptime(data['time_submitted'], "%Y-%m-%dT%H:%M:%SZ") + time_to_expire = datetime.strptime(data['time_to_expire'], "%Y-%m-%dT%H:%M:%SZ") + delta = timedelta(hours=24) self.assertEqual(time_to_expire - time_submitted, delta) @patch.object(odcs.server.config.Config, 'auth_backend', new_callable=PropertyMock) @@ -420,3 +430,92 @@ class TestViews(ModelsBaseTest): db.session.expire_all() c = db.session.query(Compose).filter(Compose.id == 1).one() self.assertEqual(c.state, COMPOSE_STATES["wait"]) + + +class TestExtendExpiration(ViewBaseTest): + """Test view post to extend expiration""" + + def setup_test_data(self): + self.initial_datetime = datetime(year=2016, month=1, day=1, + hour=0, minute=0, second=0) + with freeze_time(self.initial_datetime): + self.c1 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f25", + COMPOSE_RESULTS["repository"], 60) + self.c2 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f26", + COMPOSE_RESULTS["repository"], 60) + self.c3 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "f27", + COMPOSE_RESULTS["repository"], 60) + self.c4 = Compose.create( + db.session, "me", PungiSourceType.KOJI_TAG, "master", + COMPOSE_RESULTS["repository"], 60) + + map(db.session.add, (self.c1, self.c2, self.c3, self.c4)) + db.session.commit() + + self.c1.reused_id = self.c2.id + self.c2.reused_id = self.c3.id + db.session.commit() + + # Store for retrieving them back for assertion + self.c1_id = self.c1.id + self.c3_id = self.c3.id + + def test_fail_if_extend_non_existing_compose(self): + post_data = json.dumps({ + 'id': 999, + 'seconds-to-live': 600 + }) + with self.test_request_context(): + rv = self.client.post('/odcs/1/composes/', data=post_data) + data = json.loads(rv.data.decode('utf8')) + + self.assertEqual('No compose with id 999 found', data['message']) + + def test_fail_if_compose_is_not_done(self): + self.c1.state = COMPOSE_STATES['wait'] + db.session.commit() + + post_data = json.dumps({ + 'id': self.c1.id, + 'seconds-to-live': 600 + }) + with self.test_request_context(): + rv = self.client.post('/odcs/1/composes/', data=post_data) + data = json.loads(rv.data.decode('utf8')) + + self.assertEqual('No compose with id {0} found'.format(self.c1.id), + data['message']) + + def test_extend_compose_expiration(self): + fake_utcnow = datetime.utcnow() + + self.c2.state = COMPOSE_STATES['done'] + self.c2.time_to_expire = fake_utcnow - timedelta(seconds=10) + db.session.commit() + + expected_seconds_to_live = 60 * 60 * 3 + expected_time_to_expire = fake_utcnow + timedelta( + seconds=expected_seconds_to_live) + post_data = json.dumps({ + 'id': self.c2.id, + 'seconds-to-live': expected_seconds_to_live + }) + + with self.test_request_context(): + with freeze_time(fake_utcnow): + rv = self.client.post('/odcs/1/composes/', data=post_data) + data = json.loads(rv.data.decode('utf8')) + + self.assertEqual( + Compose._utc_datetime_to_iso(expected_time_to_expire), + data['time_to_expire']) + + # Compose reusing self.c2 and the one self.c2 reuses should also be + # extended. + c1 = db.session.query(Compose).filter(Compose.id == self.c1_id).first() + self.assertEqual(expected_time_to_expire, c1.time_to_expire) + c3 = db.session.query(Compose).filter(Compose.id == self.c3_id).first() + self.assertEqual(expected_time_to_expire, c3.time_to_expire)