Skip to content

Commit

Permalink
Add hermes subscribers for Service (allegro#2945)
Browse files Browse the repository at this point in the history
  • Loading branch information
szok authored and xliiv committed Jan 26, 2017
1 parent e2ed52e commit 11a94fb
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/ralph/assets/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'ralph.assets.apps.AssetsConfig'
12 changes: 12 additions & 0 deletions src/ralph/assets/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.conf import settings
from ralph.apps import RalphAppConfig


class AssetsConfig(RalphAppConfig):

name = 'ralph.assets'

def get_load_modules_when_ready(self):
if settings.ENABLE_HERMES_INTEGRATION:
return ['subscribers']
return []
158 changes: 158 additions & 0 deletions src/ralph/assets/subscribers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
import logging

import pyhermes

from django.conf import settings
from django.contrib.auth import get_user_model

from ralph.assets.models.assets import Environment, Service, ServiceEnvironment
from ralph.assets.models.base import BaseObject

logger = logging.getLogger(__name__)


def _update_service_owners(service, business_owners, technical_owners):
"""
Update business and technical owners for selected service.
Args:
service: Service
business_owners: dict of owners
technical_owners: dict of owners
"""
business_owners = get_user_model().objects.filter(
username__in=[i['username'] for i in business_owners]
)
technical_owners = get_user_model().objects.filter(
username__in=[i['username'] for i in technical_owners]
)

service.business_owners = business_owners
service.technical_owners = technical_owners


def _update_service_environments(service, environments):
"""
Add and removes environments for selcted service.
Args:
service: Service
environments: list of environment names
Returns:
True if service environment updated
False if can not delete ServiceEnvironment
"""
new_envs = []
for env_name in environments:
new_envs.append(Environment.objects.get_or_create(name=env_name)[0])

current = set([e.id for e in service.environments.all()])
new = set([e.id for e in new_envs])
to_delete = current - new
to_add = new - current
for env_id in to_add:
ServiceEnvironment.objects.create(
service=service,
environment_id=env_id
)
for env_id in to_delete:
service_env = ServiceEnvironment.objects.get(
service=service, environment_id=env_id
)
if BaseObject.objects.filter(service_env=service_env).exists():
logger.error(
'Can not delete service environment - it has assigned some base objects', # noqa: E501
extra={
'service_uid': service.uid,
'service_env': service_env
}
)
return False
service_env.delete()

return True


@pyhermes.subscriber(topic=settings.HERMES_SERVICE_TOPICS['CREATE'])
@pyhermes.subscriber(topic=settings.HERMES_SERVICE_TOPICS['UPDATE'])
@pyhermes.subscriber(topic=settings.HERMES_SERVICE_TOPICS['REFRESH'])
def update_service_handler(service_data):
"""
Update information about Service from Hermes event.
Add, update or set active to False if Service has deleted.
Example 'service_data' structures:
{
'uid': 'service uid',
'name': 'service name',
'status': 'service status',
'isActive': 'boolean if service is active',
'environments': 'service environments',
'businessOwners': [
{'username': 'username'}
],
'technicalOwners': [
{'username': 'username'}
]
}
"""
try:
service, _ = Service.objects.update_or_create(
uid=service_data['uid'],
defaults={
'name': service_data['name'],
}
)
except Exception as e:
logger.exception(e, extra=service_data)
else:
_update_service_owners(
service=service,
business_owners=service_data['businessOwners'],
technical_owners=service_data['technicalOwners']
)
update_envs = _update_service_environments(
service=service,
environments=service_data['environments']
)
if update_envs:
service.active = service_data['isActive']
service.save()

logger.info(
'Synced service `{}` with UID `{}`.'.format(
service_data['name'], service_data['uid']
),
extra=service_data
)


@pyhermes.subscriber(topic=settings.HERMES_SERVICE_TOPICS['DELETE'])
def delete_service_handler(service_data):
"""
Set service active to False if service deleted.
"""
try:
service_envs = ServiceEnvironment.objects.filter(
service__uid=service_data['uid']
)
if BaseObject.objects.filter(service_env__in=service_envs).exists():
logger.error(
'Can not delete service - it has assigned some base objects',
extra=service_data
)
return

Service.objects.filter(uid=service_data['uid']).update(active=False)
except Exception as e:
logger.exception(e, extra=service_data)
else:
logger.info(
'Service `{}` with UID `{}` deleted.'.format(
service_data['name'], service_data['uid']
),
extra=service_data
)
179 changes: 179 additions & 0 deletions src/ralph/assets/tests/test_subscribers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
import json
from unittest.mock import patch

from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase

from ralph.accounts.tests.factories import UserFactory
from ralph.assets.models import Service
from ralph.assets.tests.factories import (
ServiceEnvironmentFactory,
ServiceFactory
)
from ralph.data_center.tests.factories import DataCenterAssetFactory


class ServiceSubscribersTestCase(TestCase):

def setUp(self):
super().setUp()
for i in range(1, 4):
UserFactory(username='business_user{}'.format(i))
UserFactory(username='technical_user{}'.format(i))

def _make_request(self, event_data, subscriber_name):
response = self.client.post(
reverse(
'hermes-event-subscriber',
kwargs={
'subscriber_name': subscriber_name
}
),
json.dumps(event_data),
content_type="application/json",
follow=False
)
return response

def test_update_service_when_service_does_not_exist(self):
data = {
'uid': 'sc-001',
'name': 'TestName',
'status': 'Active',
'isActive': True,
'environments': ['prod', 'dev'],
'businessOwners': [{'username': 'business_user1'}],
'technicalOwners': [{'username': 'technical_user2'}]
}
response = self._make_request(
data, settings.HERMES_SERVICE_TOPICS['CREATE']
)
self.assertEqual(response.status_code, 204)
service = Service.objects.get(uid='sc-001')
self.assertTrue(service.active)
self.assertEqual(service.name, 'TestName')
self.assertCountEqual(
['prod', 'dev'],
[env.name for env in service.environments.all()]
)
self.assertCountEqual(
['business_user1'],
[user.username for user in service.business_owners.all()]
)
self.assertCountEqual(
['technical_user2'],
[user.username for user in service.technical_owners.all()]
)

def test_update_service_when_service_exist(self):
service = ServiceFactory()
service.business_owners.add(UserFactory(username='business_user1'))
service.technical_owners.add(UserFactory(username='technical_user2'))
ServiceEnvironmentFactory(
service=service, environment__name='prod'
)
data = {
'uid': service.uid,
'name': 'New name',
'status': 'Active',
'isActive': True,
'environments': ['dev'],
'businessOwners': [{'username': 'business_user3'}],
'technicalOwners': [{'username': 'technical_user3'}]
}
response = self._make_request(
data, settings.HERMES_SERVICE_TOPICS['UPDATE']
)
self.assertEqual(response.status_code, 204)
service.refresh_from_db()
self.assertEqual(service.name, 'New name')
self.assertCountEqual(
['dev'],
[env.name for env in service.environments.all()]
)
self.assertCountEqual(
['business_user3'],
[user.username for user in service.business_owners.all()]
)
self.assertCountEqual(
['technical_user3'],
[user.username for user in service.technical_owners.all()]
)

@patch('ralph.assets.subscribers.logger')
def test_update_service_environment_when_environment_assigned_to_object(
self, mock_logger
):
service = ServiceFactory(active=True)
service_env = ServiceEnvironmentFactory(
service=service, environment__name='prod'
)
DataCenterAssetFactory(service_env=service_env)
data = {
'uid': service.uid,
'name': 'New name',
'status': 'Active',
'isActive': False,
'environments': ['dev'],
'businessOwners': [{'username': 'business_user3'}],
'technicalOwners': [{'username': 'technical_user3'}]
}
response = self._make_request(
data, settings.HERMES_SERVICE_TOPICS['UPDATE']
)
self.assertEqual(response.status_code, 204)
service.refresh_from_db()
mock_logger.error.assert_called_with(
'Can not delete service environment - it has assigned some base objects', # noqa: E501
extra={
'service_uid': service.uid,
'service_env': service_env
}
)
service.refresh_from_db()
self.assertTrue(service.active)

def test_delete_with_valid_event_data(self):
service = ServiceFactory(active=True)
data = {
'uid': service.uid,
'name': 'Service name',
'status': 'Inactive',
'isActive': False,
'environments': ['dev'],
'businessOwners': [{'username': 'business_user1'}],
'technicalOwners': [{'username': 'technical_user1'}]
}
response = self._make_request(
data, settings.HERMES_SERVICE_TOPICS['DELETE']
)
self.assertEqual(response.status_code, 204)
service.refresh_from_db()
self.assertFalse(service.active)

@patch('ralph.assets.subscribers.logger')
def test_delete_service_when_service_assigned_to_object(self, mock_logger):
service = ServiceFactory(active=True)
service_env = ServiceEnvironmentFactory(service=service)
DataCenterAssetFactory(service_env=service_env)
data = {
'uid': service.uid,
'name': 'Service name',
'status': 'Inactive',
'isActive': False,
'environments': ['dev'],
'businessOwners': [{'username': 'business_user'}],
'technicalOwners': [{'username': 'technical_user'}]
}
response = self._make_request(
data, settings.HERMES_SERVICE_TOPICS['DELETE']
)
self.assertEqual(response.status_code, 204)
mock_logger.error.assert_called_with(
'Can not delete service - it has assigned some base objects',
extra=data
)
service.refresh_from_db()
self.assertTrue(service.active)
15 changes: 15 additions & 0 deletions src/ralph/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,21 @@ def str_to_bool(s: str) -> bool:
'HERMES_HOST_UPDATE_TOPIC_NAME', None
)

HERMES_SERVICE_TOPICS = {
'CREATE': os.environ.get(
'SERVICE_CREATE_HERMES_TOPIC_NAME', 'hermes.service.create'
),
'DELETE': os.environ.get(
'SERVICE_DELETE_HERMES_TOPIC_NAME', 'hermes.service.delete'
),
'UPDATE': os.environ.get(
'SERVICE_UPDATE_HERMES_TOPIC_NAME', 'hermes.service.update'
),
'REFRESH': os.environ.get(
'SERVICE_REFRESH_HERMES_TOPIC_NAME', 'hermes.service.refresh'
)
}

if ENABLE_HERMES_INTEGRATION:
INSTALLED_APPS += (
'pyhermes.apps.django',
Expand Down
4 changes: 3 additions & 1 deletion src/ralph/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
# {'level': 'DEBUG', 'handlers': ['console']}
# )


RQ_QUEUES['ralph_job_test'] = dict(ASYNC=False, **REDIS_CONNECTION)
RQ_QUEUES['ralph_async_transitions']['ASYNC'] = False
RALPH_INTERNAL_SERVICES.update({
Expand Down Expand Up @@ -88,3 +87,6 @@ def __getitem__(self, item):

EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
ENABLE_EMAIL_NOTIFICATION = True

ENABLE_HERMES_INTEGRATION = True
HERMES['ENABLED'] = ENABLE_HERMES_INTEGRATION

0 comments on commit 11a94fb

Please sign in to comment.