From f8b939fbcda1d52d9d60b07757d7231dc4710538 Mon Sep 17 00:00:00 2001 From: Andrew Melton Date: Wed, 30 Jan 2013 14:13:00 -0500 Subject: [PATCH 1/3] Handling of late starts for usage parsing --- stacktach/db.py | 3 +++ stacktach/test_utils.py | 3 ++- stacktach/tests.py | 19 ++++++++----------- stacktach/views.py | 11 ++++++----- tests/unit/test_stacktach.py | 26 +++++++++++++------------- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/stacktach/db.py b/stacktach/db.py index 23db4fb..1d31a2e 100644 --- a/stacktach/db.py +++ b/stacktach/db.py @@ -27,6 +27,9 @@ def find_request_trackers(**kwargs): def create_instance_usage(**kwargs): return models.InstanceUsage(**kwargs) +def get_or_create_instance_usage(**kwargs): + return models.InstanceUsage.objects.get_or_create(**kwargs) + def get_instance_usage(**kwargs): return models.InstanceUsage.objects.get(**kwargs) diff --git a/stacktach/test_utils.py b/stacktach/test_utils.py index 9f14069..7625b91 100644 --- a/stacktach/test_utils.py +++ b/stacktach/test_utils.py @@ -160,10 +160,11 @@ def make_resize_revert_end_json(launched_at, instance_type_id='1', def create_raw(deployment, when, event, instance=INSTANCE_ID_1, request_id=REQUEST_ID_1, state='active', old_task='', - host='compute', json=''): + host='compute', service='compute', json=''): raw_values = { 'deployment': deployment, 'host': host, + 'service': service, 'state': state, 'old_task': old_task, 'when': when, diff --git a/stacktach/tests.py b/stacktach/tests.py index 7024c8e..b3a318e 100644 --- a/stacktach/tests.py +++ b/stacktach/tests.py @@ -35,7 +35,7 @@ class ViewsLifecycleWorkflowTestCase(unittest.TestCase): when3 = views.str_time_to_unix('2012-12-21 12:36:56.124') self.update_raw = create_raw(self.deployment, when1, 'compute.instance.update', - host='api') + host='api', service='api') self.start_raw = create_raw(self.deployment, when2, 'compute.instance.reboot.start') self.end_raw = create_raw(self.deployment, when3, @@ -105,7 +105,7 @@ class ViewsLifecycleWorkflowTestCase(unittest.TestCase): 'compute.instance.update', instance=INSTANCE_ID_2, request_id=REQUEST_ID_2, - host='api') + host='api', service='api') start_raw2 = create_raw(self.deployment, when2, 'compute.instance.resize.start', instance=INSTANCE_ID_2, @@ -157,7 +157,7 @@ class ViewsLifecycleWorkflowTestCase(unittest.TestCase): update_raw2 = create_raw(self.deployment, when1, 'compute.instance.update', request_id=REQUEST_ID_2, - host='api') + host='api', service='api') start_raw2 = create_raw(self.deployment, when2, 'compute.instance.resize.start', request_id=REQUEST_ID_2) @@ -227,7 +227,7 @@ class ViewsLifecycleWorkflowTestCase(unittest.TestCase): 'compute.instance.update', instance=INSTANCE_ID_2, request_id=REQUEST_ID_2, - host='api') + host='api', service='api') start_raw2 = create_raw(self.deployment, when2, 'compute.instance.resize.start', instance=INSTANCE_ID_2, @@ -268,9 +268,9 @@ class ViewsLifecycleWorkflowTestCase(unittest.TestCase): when2 = views.str_time_to_unix('2012-12-21 13:34:50.123') when3 = views.str_time_to_unix('2012-12-21 13:37:50.124') update_raw2 = create_raw(self.deployment, when1, - 'compute.instance.update', - request_id=REQUEST_ID_2, - host='api') + 'compute.instance.update', + request_id=REQUEST_ID_2, + host='api', service='api') start_raw2 = create_raw(self.deployment, when2, 'compute.instance.resize.start', request_id=REQUEST_ID_2) @@ -309,7 +309,7 @@ class ViewsLifecycleWorkflowTestCase(unittest.TestCase): update_raw2 = create_raw(self.deployment, when1, 'compute.instance.update', request_id=REQUEST_ID_2, - host='api') + host='api', service='api') start_raw2 = create_raw(self.deployment, when2, 'compute.instance.resize.start', request_id=REQUEST_ID_2) @@ -621,7 +621,6 @@ class ViewsUsageWorkflowTestCase(unittest.TestCase): usage = usages[0] self.assertOnUsage(usage, INSTANCE_ID_1, '1', launched, REQUEST_ID_1) - @unittest.skip("can't handle late starts yet") def test_create_workflow_start_late(self): created_str = '2012-12-21 06:30:50.123' created = views.str_time_to_unix(created_str) @@ -749,7 +748,6 @@ class ViewsUsageWorkflowTestCase(unittest.TestCase): self.assertOnUsage(usage_after, INSTANCE_ID_1, '2', finish_time, REQUEST_ID_2) - @unittest.skip("can't handle late starts yet") def test_resize_workflow_start_late(self): launched_str = '2012-12-21 06:34:50.123' launched = views.str_time_to_unix(launched_str) @@ -852,7 +850,6 @@ class ViewsUsageWorkflowTestCase(unittest.TestCase): self.assertOnUsage(usage_after_revert, INSTANCE_ID_1, '1', end_time, REQUEST_ID_3) - @unittest.skip("can't handle late starts yet") def test_resize_revert_workflow_start_late(self): launched_str = '2012-12-21 06:34:50.123' launched = views.str_time_to_unix(launched_str) diff --git a/stacktach/views.py b/stacktach/views.py index 47f9807..55fc520 100644 --- a/stacktach/views.py +++ b/stacktach/views.py @@ -221,10 +221,11 @@ def _process_usage_for_new_launch(raw): values['instance'] = payload['instance_id'] values['request_id'] = notif[1]['_context_request_id'] - if raw.event == INSTANCE_EVENT['create_start']: - values['instance_type_id'] = payload['instance_type_id'] + (usage, new) = STACKDB.get_or_create_instance_usage(**values) + + if raw.event == INSTANCE_EVENT['create_start']: + usage.instance_type_id = payload['instance_type_id'] - usage = STACKDB.create_instance_usage(**values) STACKDB.save(usage) @@ -233,8 +234,8 @@ def _process_usage_for_updates(raw): payload = notif[1]['payload'] instance_id = payload['instance_id'] request_id = notif[1]['_context_request_id'] - usage = STACKDB.get_instance_usage(instance=instance_id, - request_id=request_id) + (usage, new) = STACKDB.get_or_create_instance_usage(instance=instance_id, + request_id=request_id) if raw.event in [INSTANCE_EVENT['create_end'], INSTANCE_EVENT['resize_finish_end'], diff --git a/tests/unit/test_stacktach.py b/tests/unit/test_stacktach.py index 576e306..56774db 100644 --- a/tests/unit/test_stacktach.py +++ b/tests/unit/test_stacktach.py @@ -410,13 +410,13 @@ class StacktackUsageParsingTestCase(unittest.TestCase): event = 'compute.instance.create.start' raw = utils.create_raw(self.mox, when, event=event, json_str=json_str) usage = self.mox.CreateMockAnything() - views.STACKDB.create_instance_usage(instance=INSTANCE_ID_1, - request_id=REQUEST_ID_1, - instance_type_id = '1')\ - .AndReturn(usage) + views.STACKDB.get_or_create_instance_usage(instance=INSTANCE_ID_1, + request_id=REQUEST_ID_1)\ + .AndReturn((usage, True)) views.STACKDB.save(usage) self.mox.ReplayAll() views._process_usage_for_new_launch(raw) + self.assertEquals(usage.instance_type_id, '1') self.mox.VerifyAll() def test_process_usage_for_updates_create_end(self): @@ -433,9 +433,9 @@ class StacktackUsageParsingTestCase(unittest.TestCase): usage.instance = INSTANCE_ID_1 usage.request_id = REQUEST_ID_1 usage.instance_type_id = '1' - views.STACKDB.get_instance_usage(instance=INSTANCE_ID_1, - request_id=REQUEST_ID_1)\ - .AndReturn(usage) + views.STACKDB.get_or_create_instance_usage(instance=INSTANCE_ID_1, + request_id=REQUEST_ID_1)\ + .AndReturn((usage, True)) views.STACKDB.save(usage) self.mox.ReplayAll() @@ -459,9 +459,9 @@ class StacktackUsageParsingTestCase(unittest.TestCase): usage.instance = INSTANCE_ID_1 usage.request_id = REQUEST_ID_1 usage.instance_type_id = '1' - views.STACKDB.get_instance_usage(instance=INSTANCE_ID_1, - request_id=REQUEST_ID_1)\ - .AndReturn(usage) + views.STACKDB.get_or_create_instance_usage(instance=INSTANCE_ID_1, + request_id=REQUEST_ID_1)\ + .AndReturn((usage, True)) views.STACKDB.save(usage) self.mox.ReplayAll() @@ -484,9 +484,9 @@ class StacktackUsageParsingTestCase(unittest.TestCase): usage = self.mox.CreateMockAnything() usage.instance = INSTANCE_ID_1 usage.request_id = REQUEST_ID_1 - views.STACKDB.get_instance_usage(instance=INSTANCE_ID_1, - request_id=REQUEST_ID_1)\ - .AndReturn(usage) + views.STACKDB.get_or_create_instance_usage(instance=INSTANCE_ID_1, + request_id=REQUEST_ID_1)\ + .AndReturn((usage, True)) views.STACKDB.save(usage) self.mox.ReplayAll() From 57c696d6279d27a4dc2edee0b77814631e75443f Mon Sep 17 00:00:00 2001 From: Andrew Melton Date: Thu, 31 Jan 2013 13:46:04 -0500 Subject: [PATCH 2/3] Allowing for delete events before launches --- stacktach/db.py | 3 +++ stacktach/models.py | 16 ++++++++++++---- stacktach/tests.py | 20 +++++++------------- stacktach/views.py | 16 ++++++++++------ tests/unit/test_stacktach.py | 27 +++++++++++++-------------- 5 files changed, 45 insertions(+), 37 deletions(-) diff --git a/stacktach/db.py b/stacktach/db.py index 1d31a2e..f548b9d 100644 --- a/stacktach/db.py +++ b/stacktach/db.py @@ -33,6 +33,9 @@ def get_or_create_instance_usage(**kwargs): def get_instance_usage(**kwargs): return models.InstanceUsage.objects.get(**kwargs) +def create_instance_delete(**kwargs): + return models.InstanceDeletes(**kwargs) + def create_instance_exists(**kwargs): return models.InstanceExists(**kwargs) diff --git a/stacktach/models.py b/stacktach/models.py index ca2ea94..11d56f0 100644 --- a/stacktach/models.py +++ b/stacktach/models.py @@ -72,18 +72,26 @@ class Lifecycle(models.Model): class InstanceUsage(models.Model): instance = models.CharField(max_length=50, null=True, blank=True, db_index=True) - #launched_at = models.IntegerField(null=True, db_index=True) launched_at = models.DecimalField(null=True, max_digits=20, decimal_places=6) - #deleted_at = models.IntegerField(null=True, db_index=True) - deleted_at = models.DecimalField(null=True, max_digits=20, - decimal_places=6) request_id = models.CharField(max_length=50, null=True, blank=True, db_index=True) instance_type_id = models.CharField(max_length=50, null=True, blank=True, db_index=True) + + +class InstanceDeletes(models.Model): + instance = models.CharField(max_length=50, null=True, + blank=True, db_index=True) + launched_at = models.DecimalField(null=True, max_digits=20, + decimal_places=6) + deleted_at = models.DecimalField(null=True, max_digits=20, + decimal_places=6) + raw = models.ForeignKey(RawData, null=True) + + class InstanceExists(models.Model): PENDING = 'pending' VERIFIED = 'verified' diff --git a/stacktach/tests.py b/stacktach/tests.py index b3a318e..9930695 100644 --- a/stacktach/tests.py +++ b/stacktach/tests.py @@ -492,14 +492,6 @@ class ViewsUsageTestCase(unittest.TestCase): def test_process_delete(self): launched_str = '2012-12-21 06:34:50.123' launched = views.str_time_to_unix(launched_str) - values = { - 'instance': INSTANCE_ID_1, - 'request_id': REQUEST_ID_1, - 'instance_type_id': '1', - 'launched_at': launched, - } - InstanceUsage(**values).save() - deleted_str = '2012-12-21 12:34:50.123' deleted = views.str_time_to_unix(deleted_str) json = test_utils.make_delete_end_json(launched_str, deleted_str) @@ -508,10 +500,13 @@ class ViewsUsageTestCase(unittest.TestCase): views._process_delete(raw) - usages = InstanceUsage.objects.all() - self.assertEqual(len(usages), 1) - usage = usages[0] - self.assertEqual(usage.deleted_at, deleted) + delete = InstanceDeletes.objects.all() + self.assertEqual(len(delete), 1) + delete = delete[0] + self.assertEqual(delete.instance, INSTANCE_ID_1) + self.assertEqual(delete.launched_at, launched) + self.assertEqual(delete.deleted_at, deleted) + self.assertEqual(delete.raw.id, raw.id) def test_process_exists(self): launched_str = '2012-12-21 06:34:50.123' @@ -556,7 +551,6 @@ class ViewsUsageTestCase(unittest.TestCase): 'request_id': REQUEST_ID_1, 'instance_type_id': '1', 'launched_at': launched, - 'deleted_at': deleted, } InstanceUsage(**values).save() diff --git a/stacktach/views.py b/stacktach/views.py index 55fc520..eb67e38 100644 --- a/stacktach/views.py +++ b/stacktach/views.py @@ -254,12 +254,16 @@ def _process_delete(raw): notif = json.loads(raw.json) payload = notif[1]['payload'] instance_id = payload['instance_id'] - launched_at = payload['launched_at'] - launched_at = str_time_to_unix(launched_at) - instance = STACKDB.get_instance_usage(instance=instance_id, - launched_at=launched_at) - instance.deleted_at = str_time_to_unix(payload['deleted_at']) - STACKDB.save(instance) + launched_at = str_time_to_unix(payload['launched_at']) + deleted_at = str_time_to_unix(payload['deleted_at']) + values = { + 'instance': instance_id, + 'launched_at': launched_at, + 'deleted_at': deleted_at, + 'raw': raw + } + delete = STACKDB.create_instance_delete(**values) + STACKDB.save(delete) def _process_exists(raw): diff --git a/tests/unit/test_stacktach.py b/tests/unit/test_stacktach.py index 56774db..9fa9e52 100644 --- a/tests/unit/test_stacktach.py +++ b/tests/unit/test_stacktach.py @@ -508,23 +508,22 @@ class StacktackUsageParsingTestCase(unittest.TestCase): event = 'compute.instance.delete.end' raw = utils.create_raw(self.mox, delete_decimal, event=event, json_str=json_str) - usage = self.mox.CreateMockAnything() - usage.instance = INSTANCE_ID_1 - usage.request_id = REQUEST_ID_1 - usage.instance_type_id = '1' - usage.launched_at = launch_decimal - views.STACKDB.get_instance_usage(instance=INSTANCE_ID_1, - launched_at=launch_decimal)\ - .AndReturn(usage) - views.STACKDB.save(usage) + delete = self.mox.CreateMockAnything() + delete.instance = INSTANCE_ID_1 + delete.launched_at = launch_decimal + delete.deleted_at = delete_decimal + views.STACKDB.create_instance_delete(instance=INSTANCE_ID_1, + launched_at=launch_decimal, + deleted_at=delete_decimal, + raw=raw)\ + .AndReturn(delete) + views.STACKDB.save(delete) self.mox.ReplayAll() views._process_delete(raw) - self.assertEqual(usage.instance, INSTANCE_ID_1) - self.assertEqual(usage.request_id, REQUEST_ID_1) - self.assertEqual(usage.instance_type_id, '1') - self.assertEqual(usage.launched_at, launch_decimal) - self.assertEqual(usage.deleted_at, delete_decimal) + self.assertEqual(delete.instance, INSTANCE_ID_1) + self.assertEqual(delete.launched_at, launch_decimal) + self.assertEqual(delete.deleted_at, delete_decimal) self.mox.VerifyAll() def test_process_exists(self): From 05b4cb38dd3d46434ab4eda447720d969f628aa3 Mon Sep 17 00:00:00 2001 From: Andrew Melton Date: Fri, 1 Feb 2013 11:07:13 -0500 Subject: [PATCH 3/3] Finishing unordered-deletes, modifying db queries for low resolution timestamps from Nova --- stacktach/db.py | 30 +++++++++++++++++++++++++++++- stacktach/models.py | 3 +-- stacktach/views.py | 13 +++++++++---- tests/unit/test_stacktach.py | 14 ++++++++++++-- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/stacktach/db.py b/stacktach/db.py index f548b9d..a55b4df 100644 --- a/stacktach/db.py +++ b/stacktach/db.py @@ -1,43 +1,71 @@ import models + def get_or_create_deployment(name): return models.Deployment.objects.get_or_create(name=name) + def create_rawdata(**kwargs): return models.RawData(**kwargs) + def create_lifecycle(**kwargs): return models.Lifecycle(**kwargs) + def find_lifecycles(**kwargs): return models.Lifecycle.objects.select_related().filter(**kwargs) + def create_timing(**kwargs): return models.Timing(**kwargs) + def find_timings(**kwargs): return models.Timing.objects.select_related().filter(**kwargs) + def create_request_tracker(**kwargs): return models.RequestTracker(**kwargs) + def find_request_trackers(**kwargs): return models.RequestTracker.objects.filter(**kwargs) + def create_instance_usage(**kwargs): return models.InstanceUsage(**kwargs) + def get_or_create_instance_usage(**kwargs): return models.InstanceUsage.objects.get_or_create(**kwargs) + def get_instance_usage(**kwargs): - return models.InstanceUsage.objects.get(**kwargs) + usage = None + try: + usage = models.InstanceUsage.objects.get(**kwargs) + except models.InstanceUsage.DoesNotExist: + pass + return usage + def create_instance_delete(**kwargs): return models.InstanceDeletes(**kwargs) + +def get_instance_delete(**kwargs): + delete = None + try: + delete = models.InstanceDeletes.objects.get(**kwargs) + except models.InstanceDeletes.DoesNotExist: + pass + return delete + + def create_instance_exists(**kwargs): return models.InstanceExists(**kwargs) + def save(obj): obj.save() \ No newline at end of file diff --git a/stacktach/models.py b/stacktach/models.py index 11d56f0..e51efb5 100644 --- a/stacktach/models.py +++ b/stacktach/models.py @@ -103,10 +103,8 @@ class InstanceExists(models.Model): ] instance = models.CharField(max_length=50, null=True, blank=True, db_index=True) - #launched_at = models.IntegerField(null=True, db_index=True) launched_at = models.DecimalField(null=True, max_digits=20, decimal_places=6) - #deleted_at = models.IntegerField(null=True, db_index=True) deleted_at = models.DecimalField(null=True, max_digits=20, decimal_places=6) message_id = models.CharField(max_length=50, null=True, @@ -120,6 +118,7 @@ class InstanceExists(models.Model): default=PENDING) raw = models.ForeignKey(RawData, related_name='+', null=True) usage = models.ForeignKey(InstanceUsage, related_name='+', null=True) + delete = models.ForeignKey(InstanceDeletes, related_name='+', null=True) class Timing(models.Model): diff --git a/stacktach/views.py b/stacktach/views.py index eb67e38..6ef6571 100644 --- a/stacktach/views.py +++ b/stacktach/views.py @@ -270,16 +270,21 @@ def _process_exists(raw): notif = json.loads(raw.json) payload = notif[1]['payload'] instance_id = payload['instance_id'] - launched_at = payload['launched_at'] - launched_at = str_time_to_unix(launched_at) + launched_at = str_time_to_unix(payload['launched_at']) + launched_range = (launched_at, launched_at+1) usage = STACKDB.get_instance_usage(instance=instance_id, - launched_at=launched_at) + launched_at__range=launched_range) + delete = STACKDB.get_instance_delete(instance=instance_id, + launched_at__range=launched_range) values = {} values['message_id'] = notif[1]['message_id'] values['instance'] = instance_id values['launched_at'] = launched_at values['instance_type_id'] = payload['instance_type_id'] - values['usage'] = usage + if usage: + values['usage'] = usage + if delete: + values['delete'] = delete values['raw'] = raw deleted_at = payload.get('deleted_at') diff --git a/tests/unit/test_stacktach.py b/tests/unit/test_stacktach.py index 9fa9e52..34293ab 100644 --- a/tests/unit/test_stacktach.py +++ b/tests/unit/test_stacktach.py @@ -537,9 +537,13 @@ class StacktackUsageParsingTestCase(unittest.TestCase): raw = utils.create_raw(self.mox, current_decimal, event=event, json_str=json_str) usage = self.mox.CreateMockAnything() + launched_range = (launch_decimal, launch_decimal+1) views.STACKDB.get_instance_usage(instance=INSTANCE_ID_1, - launched_at=launch_decimal)\ + launched_at__range=launched_range)\ .AndReturn(usage) + views.STACKDB.get_instance_delete(instance=INSTANCE_ID_1, + launched_at__range=launched_range)\ + .AndReturn(None) exists_values = { 'message_id': MESSAGE_ID_1, 'instance': INSTANCE_ID_1, @@ -569,9 +573,14 @@ class StacktackUsageParsingTestCase(unittest.TestCase): raw = utils.create_raw(self.mox, current_decimal, event=event, json_str=json_str) usage = self.mox.CreateMockAnything() + launched_range = (launch_decimal, launch_decimal+1) views.STACKDB.get_instance_usage(instance=INSTANCE_ID_1, - launched_at=launch_decimal)\ + launched_at__range=launched_range)\ .AndReturn(usage) + delete = self.mox.CreateMockAnything() + views.STACKDB.get_instance_delete(instance=INSTANCE_ID_1, + launched_at__range=launched_range)\ + .AndReturn(delete) exists_values = { 'message_id': MESSAGE_ID_1, 'instance': INSTANCE_ID_1, @@ -579,6 +588,7 @@ class StacktackUsageParsingTestCase(unittest.TestCase): 'deleted_at': deleted_decimal, 'instance_type_id': '1', 'usage': usage, + 'delete': delete, 'raw': raw, } exists = self.mox.CreateMockAnything()