Skip to content

Commit f74a76e

Browse files
Merge pull request #1096 from scieloorg/beta
Incorporação de códigos estáveis
2 parents 6706446 + 5468224 commit f74a76e

27 files changed

+649
-231
lines changed

.travis.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
language: python
22
python:
33
- 2.7
4-
services:
5-
- mongodb
64
env:
7-
- DJANGO_VERSION=1.4
5+
- DJANGO_VERSION=1.4 SCIELOMANAGER_SETTINGS_FILE=`pwd`/scielomanager/scielomanager/settings_local.include
86
install:
97
- pip install -r requirements.txt --use-mirrors
108
- pip install -r requirements-test.txt --use-mirrors

Makefile

+11-10
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,37 @@ FIXTURES_DIR = $(APP_PATH)/scielomanager/fixtures
66

77
deps:
88
@pip install -r requirements.txt
9-
@pip install -r requirements-test.txt
109

1110
clean:
11+
@echo "Removing all .pyc files..."
1212
@find . -name "*.pyc" -delete
1313

14-
test: clean
14+
test:
1515
@python $(MANAGE) test --settings=$(SETTINGS_TEST)
1616

17-
testfast: clean
17+
testfast:
1818
@python $(MANAGE) test --settings=$(SETTINGS_TEST) --failfast
1919

2020
dbsetup:
2121
@python $(MANAGE) syncdb --settings=$(SETTINGS)
22-
@python $(MANAGE) loaddata $(FIXTURES_DIR)/groups.json --settings=$(SETTINGS)
2322

2423
loaddata:
24+
@python $(MANAGE) loaddata $(FIXTURES_DIR)/groups.json --settings=$(SETTINGS)
2525
@python $(MANAGE) loaddata $(APP_PATH)/journalmanager/fixtures/use_licenses.json --settings=$(SETTINGS)
2626
@python $(MANAGE) loaddata $(FIXTURES_DIR)/subject_categories.json --settings=$(SETTINGS)
2727
@python $(MANAGE) loaddata $(FIXTURES_DIR)/study_area.json --settings=$(SETTINGS)
28+
@python $(MANAGE) sync_perms --settings=$(SETTINGS)
2829

2930
dbmigrate:
3031
@python $(MANAGE) migrate --settings=$(SETTINGS)
3132

3233
compilemessages:
33-
@cd $(APP_PATH) && python manage.py compilemessages --settings=$(SETTINGS)
34+
@python $(MANAGE) compilemessages --settings=$(SETTINGS)
3435

35-
setup: deps dbsetup dbmigrate loaddata compilemessages test refreshsecretkey
36+
compile:
37+
@echo "Compiling all source files..."
38+
@cd $(APP_PATH) && python -m compileall .
3639

37-
upgrade: deps dbmigrate compilemessages test
38-
@python $(MANAGE) sync_perms --settings=$(SETTINGS)
40+
setup: clean compile deps dbsetup dbmigrate loaddata compilemessages test
3941

40-
refreshsecretkey:
41-
@sed -e 's:^\(SECRET_KEY\).*$$:\1 = '" '`openssl rand -base64 32`' "':g' -i $(APP_PATH)/settings.py
42+
upgrade: clean compile deps dbmigrate compilemessages

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ django-celery
2323
django-kombu
2424
defusedxml==0.4.1
2525
django-countries
26+
zerorpc>=0.4.4

scielomanager/api/resources_v2.py

-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ def build_filters(self, filters=None):
120120
param_filters['journal__eletronic_issn'] = filters['journal_eissn']
121121

122122
if 'journal_pissn' in filters:
123-
import pdb; pdb.set_trace()
124123
param_filters['journal__print_issn'] = filters['journal_pissn']
125124

126125
sections = models.Section.objects.filter(**param_filters)

scielomanager/health/__init__.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# coding: utf-8
2+
import datetime
3+
import threading
4+
import logging
5+
6+
import zerorpc
7+
from django.conf import settings
8+
9+
10+
logger = logging.getLogger(__name__)
11+
BIND_ADDR = getattr(settings, 'IPC_HEALTHD_BIND_ADDR', 'tcp://0.0.0.0:11711')
12+
13+
14+
def Server(health_endpoint):
15+
server = zerorpc.Server(health_endpoint)
16+
server.bind(BIND_ADDR)
17+
logger.info('healthd is bound to %s', BIND_ADDR)
18+
19+
return server
20+
21+
22+
def Client(**kwargs):
23+
""" Cria um cliente, já conectado ao serviço de monitoramento de
24+
saúde do sistema.
25+
26+
Argumentos nomeados serão passados diretamente na inicialização
27+
do objeto `zerorpc.Client`.
28+
"""
29+
client = zerorpc.Client(**kwargs)
30+
client.connect(BIND_ADDR)
31+
logger.info('healthd client is connected to %s', BIND_ADDR)
32+
33+
return client
34+
35+
36+
class CheckItem(object):
37+
"""
38+
Represents an item that needs to be checked.
39+
"""
40+
def __call__(self):
41+
"""
42+
Performs the check step for a specific app aspect.
43+
"""
44+
raise NotImplementedError()
45+
46+
def structured(self):
47+
return {'description': self.__class__.__doc__.strip(),
48+
'status': self()}
49+
50+
51+
class CheckList(object):
52+
"""
53+
Performs a sequence of checks related to the application health.
54+
"""
55+
def __init__(self, refresh=0.25):
56+
"""
57+
:param refresh: (optional) refresh rate in minutes.
58+
"""
59+
self.latest_report = {}
60+
61+
self._check_list = []
62+
self._refresh_rate = datetime.timedelta(minutes=refresh)
63+
self._refreshed_at = None
64+
65+
self._lock = threading.Lock()
66+
67+
def add_check(self, check):
68+
"""
69+
Add a check to the registry.
70+
71+
:param check: a callable that receives nothing as argument.
72+
"""
73+
assert isinstance(check, CheckItem)
74+
self._check_list.append(check)
75+
76+
def run(self):
77+
"""
78+
Run all checks sequentially and updates the object state.
79+
"""
80+
self.latest_report = {check.__class__.__name__: check.structured()
81+
for check in self._check_list}
82+
self._refreshed_at = datetime.datetime.now()
83+
84+
def update(self):
85+
"""
86+
Run all checks respecting the refresh rate.
87+
"""
88+
with self._lock:
89+
if self._refreshed_at is None or (
90+
self._refreshed_at + self._refresh_rate <= datetime.datetime.now()):
91+
92+
self.run()
93+
94+
def since(self):
95+
"""
96+
Total seconds since the last refresh.
97+
"""
98+
try:
99+
return str(datetime.datetime.now() - self._refreshed_at)
100+
except TypeError:
101+
# called before run any check
102+
return None
103+

scielomanager/health/checks.py

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#coding: utf-8
2+
import logging
3+
import uuid
4+
5+
import psycopg2
6+
from django.db import connections, DatabaseError
7+
from django.core.cache import cache
8+
from celery import current_app
9+
from kombu import Connection
10+
11+
from . import CheckItem
12+
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class PGConnection(CheckItem):
18+
"""
19+
Connection with the data backend
20+
"""
21+
working_status = (
22+
psycopg2.extensions.STATUS_READY,
23+
psycopg2.extensions.STATUS_BEGIN,
24+
psycopg2.extensions.STATUS_IN_TRANSACTION,
25+
psycopg2.extensions.STATUS_PREPARED,
26+
)
27+
def __init__(self):
28+
self.must_fail = False
29+
30+
for conn in connections.all():
31+
try:
32+
# Executa uma query, mesmo que inválida, apenas para
33+
# forçar a comunicação com o backend
34+
conn.cursor().execute('SELECT')
35+
except DatabaseError:
36+
# Erro esperado por conta da query inválida
37+
pass
38+
except psycopg2.OperationalError as e:
39+
self.must_fail = True
40+
41+
def __call__(self):
42+
"""
43+
Try to list table names just to reach the db.
44+
"""
45+
if self.must_fail:
46+
return False
47+
48+
for conn in connections.all():
49+
try:
50+
if conn.connection.status not in self.working_status:
51+
return False
52+
except AttributeError as e:
53+
return None
54+
else:
55+
return True
56+
57+
58+
class CachingBackend(CheckItem):
59+
"""
60+
Connection with the caching backend
61+
"""
62+
def __call__(self):
63+
key = uuid.uuid4().hex
64+
65+
try:
66+
ret_code = cache.add(key, '\x01', timeout=1)
67+
return bool(ret_code)
68+
finally:
69+
cache.delete(key)
70+
71+
72+
class CeleryConnection(CheckItem):
73+
"""
74+
Connection with celery backend.
75+
"""
76+
def __init__(self):
77+
self.broker_url = current_app.conf.BROKER_URL
78+
79+
def __call__(self):
80+
"""
81+
If backend is "django://" will return None
82+
Else: return if is connected or not
83+
"""
84+
if self.broker_url == 'django://':
85+
return None
86+
else:
87+
try:
88+
ping_response = current_app.control.ping(timeout=1)[0]
89+
ping_key = ping_response.keys()[0]
90+
status = ping_response[ping_key]['ok'] == u'pong'
91+
except Exception as exc:
92+
logger.exception(exc)
93+
status = False
94+
return status
95+
96+
97+
class RabbitConnection(CheckItem):
98+
"""
99+
Connection with RabbitMQ server.
100+
"""
101+
def __init__(self):
102+
self.broker_url = current_app.conf.BROKER_URL
103+
104+
def __call__(self):
105+
"""
106+
Broker URL must start with "amqp://" else return None
107+
Return True if can establish connection or not.
108+
"""
109+
if self.broker_url.startswith("amqp://"):
110+
try:
111+
connection = Connection(self.broker_url, connection_timeout=1)
112+
try:
113+
connection.connect()
114+
status = connection.connected
115+
finally:
116+
connection.release()
117+
except Exception as exc:
118+
logger.exception(exc)
119+
status = False
120+
return status
121+
else:
122+
return None

scielomanager/health/domain.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#coding: utf-8
2+
import logging
3+
4+
import zerorpc
5+
6+
import health
7+
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class BackendUnavailable(Exception):
13+
""" Backend de verificação de saúde está indisponível.
14+
"""
15+
16+
17+
class StatusChecker(object):
18+
""" Realiza e agrupa as verificações de saúde do sistema.
19+
"""
20+
def __init__(self, client=health.Client):
21+
self.client = client(timeout=5)
22+
23+
@property
24+
def is_fully_operational(self):
25+
return all([st['status'] for st in self.overall_status().values()])
26+
27+
def overall_status(self):
28+
if not hasattr(self, '_overall_status'):
29+
try:
30+
setattr(self, '_overall_status', self.client.overall_status())
31+
except (zerorpc.LostRemote, zerorpc.TimeoutExpired) as e:
32+
logger.exception(e)
33+
raise BackendUnavailable(e.message)
34+
35+
return self._overall_status
36+
37+
@property
38+
def elapsed_time(self):
39+
try:
40+
return self.client.elapsed_time()
41+
except (zerorpc.LostRemote, zerorpc.TimeoutExpired) as e:
42+
logger.exception(e)
43+
raise BackendUnavailable(e.message)
44+

scielomanager/health/management/__init__.py

Whitespace-only changes.

scielomanager/health/management/commands/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)