diff --git a/.gitignore b/.gitignore index 40780a1..6abacdf 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ etc/stacktach_worker_config.json etc/stacktach_verifier_config.json verifier.log verifier.log.* +.gitattributes diff --git a/stacktach/dbapi.py b/stacktach/dbapi.py index abed81f..1d0cc4d 100644 --- a/stacktach/dbapi.py +++ b/stacktach/dbapi.py @@ -79,13 +79,6 @@ def _log_api_exception(cls, ex, request): stacklog.error(msg) -def _exists_model_factory(service): - if service == 'glance': - return models.ImageExists - elif service == 'nova': - return models.InstanceExists - - def api_call(func): @functools.wraps(func) @@ -108,28 +101,85 @@ def api_call(func): return handled +def _usage_model_factory(service): + if service == 'nova': + return {'klass': models.InstanceUsage, 'order_by': 'launched_at'} + if service == 'glance': + return {'klass': models.ImageUsage, 'order_by': 'created_at'} + + +def _exists_model_factory(service): + if service == 'nova': + return {'klass': models.InstanceExists, 'order_by': 'id'} + if service == 'glance': + return {'klass': models.ImageExists, 'order_by': 'id'} + + +def _deletes_model_factory(service): + if service == 'nova': + return {'klass': models.InstanceDeletes, 'order_by': 'launched_at'} + if service == 'glance': + return {'klass': models.ImageDeletes, 'order_by': 'deleted_at'} + @api_call def list_usage_launches(request): - objects = get_db_objects(models.InstanceUsage, request, 'launched_at') - dicts = _convert_model_list(objects) - return {'launches': dicts} + return {'launches': list_usage_launches_with_service(request, 'nova')} +@api_call +def list_usage_images(request): + return { 'images': list_usage_launches_with_service(request, 'glance')} + + +def list_usage_launches_with_service(request, service): + model = _usage_model_factory(service) + objects = get_db_objects(model['klass'], request, + model['order_by']) + dicts = _convert_model_list(objects) + return dicts + + +def get_usage_launch_with_service(launch_id, service): + model = _usage_model_factory(service) + return {'launch': _get_model_by_id(model['klass'], launch_id)} @api_call def get_usage_launch(request, launch_id): - return {'launch': _get_model_by_id(models.InstanceUsage, launch_id)} + return get_usage_launch_with_service(launch_id, 'nova') + + +@api_call +def get_usage_image(request, image_id): + return get_usage_launch_with_service(image_id, 'glance') @api_call def list_usage_deletes(request): - objects = get_db_objects(models.InstanceDeletes, request, 'launched_at') + return list_usage_deletes_with_service(request, 'nova') + + +@api_call +def list_usage_deletes_glance(request): + return list_usage_deletes_with_service(request, 'glance') + + +def list_usage_deletes_with_service(request, service): + model = _deletes_model_factory(service) + objects = get_db_objects(model['klass'], request, + model['order_by']) dicts = _convert_model_list(objects) return {'deletes': dicts} @api_call def get_usage_delete(request, delete_id): - return {'delete': _get_model_by_id(models.InstanceDeletes, delete_id)} + model = _deletes_model_factory('nova') + return {'delete': _get_model_by_id(model['klass'], delete_id)} + + +@api_call +def get_usage_delete_glance(request, delete_id): + model = _deletes_model_factory('glance') + return {'delete': _get_model_by_id(model['klass'], delete_id)} def _exists_extra_values(exist): @@ -139,6 +189,16 @@ def _exists_extra_values(exist): @api_call def list_usage_exists(request): + return list_usage_exists_with_service(request, 'nova') + + +@api_call +def list_usage_exists_glance(request): + return list_usage_exists_with_service(request, 'glance') + + +def list_usage_exists_with_service(request, service): + model = _exists_model_factory(service) try: custom_filters = {} if 'received_min' in request.GET: @@ -155,7 +215,7 @@ def list_usage_exists(request): msg = "Range filters must be dates." raise BadRequestException(message=msg) - objects = get_db_objects(models.InstanceExists, request, 'id', + objects = get_db_objects(model['klass'], request, 'id', custom_filters=custom_filters) dicts = _convert_model_list(objects, _exists_extra_values) return {'exists': dicts} @@ -166,6 +226,11 @@ def get_usage_exist(request, exist_id): return {'exist': _get_model_by_id(models.InstanceExists, exist_id, _exists_extra_values)} +@api_call +def get_usage_exist_glance(request, exist_id): + return {'exist': _get_model_by_id(models.ImageExists, exist_id, + _exists_extra_values)} + @api_call def exists_send_status(request, message_id): @@ -210,7 +275,7 @@ def _find_exists_with_message_id(msg_id, exists_model, service): def _ping_processing_with_service(pings, service): - exists_model = _exists_model_factory(service) + exists_model = _exists_model_factory(service)['klass'] with transaction.commit_on_success(): for msg_id, status_code in pings.items(): try: diff --git a/stacktach/urls.py b/stacktach/urls.py index 8503787..5579957 100644 --- a/stacktach/urls.py +++ b/stacktach/urls.py @@ -36,15 +36,37 @@ urlpatterns = patterns('', url(r'db/usage/launches/$', 'stacktach.dbapi.list_usage_launches'), + url(r'db/usage/nova/launches/$', + 'stacktach.dbapi.list_usage_launches'), + url(r'db/usage/glance/images/$', + 'stacktach.dbapi.list_usage_images'), url(r'db/usage/launches/(?P\d+)/$', 'stacktach.dbapi.get_usage_launch'), + url(r'db/usage/nova/launches/(?P\d+)/$', + 'stacktach.dbapi.get_usage_launch'), + url(r'db/usage/glance/images/(?P\d+)/$', + 'stacktach.dbapi.get_usage_image'), url(r'db/usage/deletes/$', 'stacktach.dbapi.list_usage_deletes'), + url(r'db/usage/nova/deletes/$', + 'stacktach.dbapi.list_usage_deletes'), + url(r'db/usage/glance/deletes/$', + 'stacktach.dbapi.list_usage_deletes_glance'), url(r'db/usage/deletes/(?P\d+)/$', 'stacktach.dbapi.get_usage_delete'), + url(r'db/usage/nova/deletes/(?P\d+)/$', + 'stacktach.dbapi.get_usage_delete'), + url(r'db/usage/glance/deletes/(?P\d+)/$', + 'stacktach.dbapi.get_usage_delete_glance'), url(r'db/usage/exists/$', 'stacktach.dbapi.list_usage_exists'), + url(r'db/usage/nova/exists/$', 'stacktach.dbapi.list_usage_exists'), + url(r'db/usage/glance/exists/$', 'stacktach.dbapi.list_usage_exists_glance'), url(r'db/usage/exists/(?P\d+)/$', 'stacktach.dbapi.get_usage_exist'), + url(r'db/usage/nova/exists/(?P\d+)/$', + 'stacktach.dbapi.get_usage_exist'), + url(r'db/usage/glance/exists/(?P\d+)/$', + 'stacktach.dbapi.get_usage_exist_glance'), url(r'db/confirm/usage/exists/(?P[\w\-]+)/$', 'stacktach.dbapi.exists_send_status'), diff --git a/tests/unit/test_dbapi.py b/tests/unit/test_dbapi.py index 1265d29..4b335a5 100644 --- a/tests/unit/test_dbapi.py +++ b/tests/unit/test_dbapi.py @@ -326,7 +326,7 @@ class DBAPITestCase(StacktachBaseTestCase): self.mox.VerifyAll() - def test_list_usage_exists_no_custom_filters(self): + def test_list_usage_exists_no_custom_filters_for_nova(self): fake_request = self.mox.CreateMockAnything() fake_request.GET = {} self.mox.StubOutWithMock(dbapi, 'get_db_objects') @@ -340,6 +340,20 @@ class DBAPITestCase(StacktachBaseTestCase): self.assertEqual(resp.status_code, 200) self.mox.VerifyAll() + def test_list_usage_exists_no_custom_filters_for_glance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + objects = self.mox.CreateMockAnything() + dbapi.get_db_objects(models.ImageExists, fake_request, 'id', + custom_filters={}).AndReturn(objects) + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(objects, dbapi._exists_extra_values) + self.mox.ReplayAll() + resp = dbapi.list_usage_exists_glance(fake_request) + self.assertEqual(resp.status_code, 200) + self.mox.VerifyAll() + def test_list_usage_exists_with_received_min(self): fake_request = self.mox.CreateMockAnything() date = str(datetime.datetime.utcnow()) @@ -361,15 +375,16 @@ class DBAPITestCase(StacktachBaseTestCase): fake_request = self.mox.CreateMockAnything() date = str(datetime.datetime.utcnow()) fake_request.GET = {'received_max': date} - self.mox.StubOutWithMock(dbapi, 'get_db_objects') unix_date = stacktach_utils.str_time_to_unix(date) custom_filters = {'received_max': {'raw__when__lte': unix_date}} objects = self.mox.CreateMockAnything() + self.mox.StubOutWithMock(dbapi, 'get_db_objects') dbapi.get_db_objects(models.InstanceExists, fake_request, 'id', custom_filters=custom_filters).AndReturn(objects) self.mox.StubOutWithMock(dbapi, '_convert_model_list') dbapi._convert_model_list(objects, dbapi._exists_extra_values) self.mox.ReplayAll() + resp = dbapi.list_usage_exists(fake_request) self.assertEqual(resp.status_code, 200) self.mox.VerifyAll() @@ -734,3 +749,175 @@ class DBAPITestCase(StacktachBaseTestCase): msg = "'messages' missing from request body" self.assertEqual(body.get('message'), msg) self.mox.VerifyAll() + + def test_list_usage_launches_without_service(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + mock_objects = self.mox.CreateMockAnything() + launches = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(mock_objects).AndReturn(launches) + dbapi.get_db_objects(models.InstanceUsage, fake_request, 'launched_at').AndReturn(mock_objects) + self.mox.ReplayAll() + + resp = dbapi.list_usage_launches(fake_request) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'launches': launches}) + self.mox.VerifyAll() + + def test_list_usage_launches_for_glance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + mock_objects = self.mox.CreateMockAnything() + launches = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(mock_objects).AndReturn(launches) + dbapi.get_db_objects(models.ImageUsage, fake_request, 'created_at').AndReturn(mock_objects) + self.mox.ReplayAll() + + resp = dbapi.list_usage_images(fake_request) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'images': launches}) + self.mox.VerifyAll() + + def test_list_usage_launches_for_nova(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + mock_objects = self.mox.CreateMockAnything() + launches = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(mock_objects).AndReturn(launches) + dbapi.get_db_objects(models.InstanceUsage, fake_request, 'launched_at').AndReturn(mock_objects) + self.mox.ReplayAll() + + resp = dbapi.list_usage_launches(fake_request) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'launches': launches}) + self.mox.VerifyAll() + + def test_get_usage_launch_with_no_service(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + launch = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_get_model_by_id') + dbapi._get_model_by_id(models.InstanceUsage, 1).AndReturn(launch) + self.mox.ReplayAll() + + resp = dbapi.get_usage_launch(fake_request, 1) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'launch': {'a': 1}}) + self.mox.VerifyAll() + + def test_get_usage_launch_for_nova(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + launch = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_get_model_by_id') + dbapi._get_model_by_id(models.InstanceUsage, 1).AndReturn(launch) + self.mox.ReplayAll() + + resp = dbapi.get_usage_launch(fake_request, 1) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'launch': {'a': 1}}) + self.mox.VerifyAll() + + def test_get_usage_launch_for_glance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + launch = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_get_model_by_id') + dbapi._get_model_by_id(models.ImageUsage, 1).AndReturn(launch) + self.mox.ReplayAll() + + resp = dbapi.get_usage_image(fake_request, 1) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'launch': {'a': 1}}) + self.mox.VerifyAll() + + def test_get_usage_delete_for_nova(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + delete = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_get_model_by_id') + dbapi._get_model_by_id(models.InstanceDeletes, 1).AndReturn(delete) + self.mox.ReplayAll() + + resp = dbapi.get_usage_delete(fake_request, 1) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'delete': {'a': 1}}) + self.mox.VerifyAll() + + def test_get_usage_delete_for_glance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + delete = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_get_model_by_id') + dbapi._get_model_by_id(models.ImageDeletes, 1).AndReturn(delete) + self.mox.ReplayAll() + + resp = dbapi.get_usage_delete_glance(fake_request, 1) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'delete': {'a': 1}}) + self.mox.VerifyAll() + + def test_list_usage_deletes_with_no_service(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + mock_objects = self.mox.CreateMockAnything() + deletes = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(mock_objects).AndReturn(deletes) + dbapi.get_db_objects(models.InstanceDeletes, fake_request, 'launched_at').AndReturn(mock_objects) + self.mox.ReplayAll() + + resp = dbapi.list_usage_deletes(fake_request) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'deletes': deletes}) + self.mox.VerifyAll() + + def test_list_usage_deletes_for_nova(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + mock_objects = self.mox.CreateMockAnything() + deletes = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(mock_objects).AndReturn(deletes) + dbapi.get_db_objects(models.InstanceDeletes, fake_request, 'launched_at').AndReturn(mock_objects) + self.mox.ReplayAll() + + resp = dbapi.list_usage_deletes(fake_request) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'deletes': deletes}) + self.mox.VerifyAll() + + def test_list_usage_deletes_for_glance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.method = 'GET' + fake_request.GET = {} + self.mox.StubOutWithMock(dbapi, 'get_db_objects') + mock_objects = self.mox.CreateMockAnything() + deletes = {'a': 1} + self.mox.StubOutWithMock(dbapi, '_convert_model_list') + dbapi._convert_model_list(mock_objects).AndReturn(deletes) + dbapi.get_db_objects(models.ImageDeletes, fake_request, 'deleted_at').AndReturn(mock_objects) + self.mox.ReplayAll() + + resp = dbapi.list_usage_deletes_glance(fake_request) + self.assertEqual(resp.status_code, 200) + self.assertEqual(json.loads(resp.content), {'deletes': deletes}) + self.mox.VerifyAll()