Skip to content

Commit 594560b

Browse files
authored
[ENG-11458] fix: osfmetrics data lost in migration (#11774)
* fix: migrate_osfmetrics_fix_6to8 make sure all items get a recent monthly usage report with cumulative counts up to this point -- previous data migration incorrectly assumed monthly usage reports were either complete or completely missing - restore some previously-removed elastic6 config and dsl types in osf.metrics.es6_metrics - change django-elasticsearch-metrics dependency to a version with elastic6 support added back in - add migrate_osfmetrics_fix_6to8 management command - shows diagnostic counts from es6 and es8 (unless --no-counts) - starts a task for each item with any past usage to add a usage report for 2026-05 with accurate cumulative view/download counts - allow running via osf-admin web ui * add es6 back to github actions * fix: orderable YearMonth * more correct counts * loggable error on non-existant osfid * more accurate comparison count * move usage report epoch to setting * finding anomalies... * further inspecting anomalies... * fix up whoopsies * better handle preprint osfids * fix some older usage reports too * use date_histogram to skip empty months * remove debugging
1 parent 931eeff commit 594560b

18 files changed

Lines changed: 881 additions & 44 deletions

File tree

.docker-compose.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ DOMAIN=http://localhost:5000/
66
INTERNAL_DOMAIN=http://192.168.168.167:5000/
77
API_DOMAIN=http://localhost:8000/
88
ELASTIC_URI=192.168.168.167:9200
9+
ELASTIC6_URI=192.168.168.167:9201
910
ELASTIC8_URI=http://192.168.168.167:9202
1011
ELASTIC8_USERNAME=elastic
1112
OSF_DB_HOST=192.168.168.167

.github/workflows/test-build.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ jobs:
4848
--health-interval 10s
4949
--health-timeout 30s
5050
--health-retries 5
51+
elasticsearch6: &ES6_SERVICE
52+
image: elasticsearch:6.8.23
53+
ports:
54+
- 9201:9200
55+
options: >-
56+
--health-cmd "curl -sf http://localhost:9200/_cluster/health?wait_for_status=yellow&timeout=30s"
57+
--health-interval 10s
58+
--health-timeout 30s
59+
--health-retries 5
5160
postgres: &POSTGRES_SERVICE
5261
image: postgres
5362
env:
@@ -67,6 +76,7 @@ jobs:
6776
run: poetry run python3 -m invoke test-ci-addons --junit
6877
env:
6978
ELASTIC8_URI: http://localhost:9202
79+
ELASTIC6_URI: http://localhost:9201
7080
- name: Upload report
7181
if: (success() || failure()) # run this step even if previous step failed
7282
uses: ./.github/actions/gen-report
@@ -94,6 +104,7 @@ jobs:
94104
checks: write
95105
services:
96106
elasticsearch8: *ES8_SERVICE
107+
elasticsearch6: *ES6_SERVICE
97108
postgres: *POSTGRES_SERVICE
98109
steps:
99110
- uses: actions/checkout@v6
@@ -104,6 +115,7 @@ jobs:
104115
run: poetry run python3 -m invoke test-ci-api1-and-js --junit
105116
env:
106117
ELASTIC8_URI: http://localhost:9202
118+
ELASTIC6_URI: http://localhost:9201
107119
- name: Upload report
108120
if: (success() || failure()) # run this step even if previous step failed
109121
uses: ./.github/actions/gen-report
@@ -115,6 +127,7 @@ jobs:
115127
checks: write
116128
services:
117129
elasticsearch8: *ES8_SERVICE
130+
elasticsearch6: *ES6_SERVICE
118131
postgres: *POSTGRES_SERVICE
119132
steps:
120133
- uses: actions/checkout@v6
@@ -123,6 +136,7 @@ jobs:
123136
run: poetry run python3 -m invoke test-ci-api2 --junit
124137
env:
125138
ELASTIC8_URI: http://localhost:9202
139+
ELASTIC6_URI: http://localhost:9201
126140
- name: Upload report
127141
if: (success() || failure()) # run this step even if previous step failed
128142
uses: ./.github/actions/gen-report
@@ -141,6 +155,7 @@ jobs:
141155
run: poetry run python3 -m invoke test-ci-api3-and-osf --junit
142156
env:
143157
ELASTIC8_URI: http://localhost:9202
158+
ELASTIC6_URI: http://localhost:9201
144159
- name: Upload report
145160
if: (success() || failure()) # run this step even if previous step failed
146161
uses: ./.github/actions/gen-report

admin/management/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.urls import re_path
1+
from django.urls import re_path, path
22

33
from admin.management import views
44

@@ -22,4 +22,5 @@
2222
name='sync_notification_templates'),
2323
re_path(r'^remove_orcid_from_user_social', views.RemoveOrcidFromUserSocial.as_view(),
2424
name='remove_orcid_from_user_social'),
25+
path('migrate_osfmetrics_fix_6to8', views.MigrateOsfmetricsFix6to8.as_view(), name='migrate_osfmetrics_fix_6to8'),
2526
]

admin/management/views.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from io import StringIO
2+
13
from dateutil.parser import isoparse
24
from django.views.generic import TemplateView, View
35
from django.contrib import messages
@@ -203,3 +205,18 @@ def post(self, request):
203205
remove_orcid_from_user_social()
204206
messages.success(request, 'Orcid from user social have been successfully removed.')
205207
return redirect(reverse('management:commands'))
208+
209+
210+
class MigrateOsfmetricsFix6to8(ManagementCommandPermissionView):
211+
def post(self, request):
212+
_command_kwargs = {
213+
'no_color': True,
214+
'no_counts': request.POST.get('no_counts'),
215+
'delete_es8_usage_reports': request.POST.get('delete_es8_usage_reports'),
216+
'start': request.POST.get('start'),
217+
}
218+
_out_io = StringIO()
219+
call_command('migrate_osfmetrics_fix_6to8', **_command_kwargs, stdout=_out_io)
220+
for _line in _out_io.getvalue().split('\n'):
221+
messages.info(request, _line)
222+
return redirect(reverse('management:commands'))

admin/templates/management/commands.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ <h4><u>Remove existing orcid info from user social</u></h4>
190190
</nav>
191191
</form>
192192
</section>
193+
<section>
194+
<h4><u>migrate osf-metrics (fix) 6to8</u></h4>
195+
<p>
196+
view progress of the osf-metrics migration (fixup) from elastic6 to elastic8 (or start it)
197+
</p>
198+
<form method="post"
199+
action="{% url 'management:migrate_osfmetrics_fix_6to8'%}"
200+
style="display: flex; flex-direction: column;">
201+
{% csrf_token %}
202+
<label><input type="checkbox" name="no_counts"> no counts</label>
203+
<label><input type="checkbox" name="delete_es8_usage_reports"> delete es8 usage reports (caution)</label>
204+
<label><input type="checkbox" name="start"> start tasks (caution)</label>
205+
<nav>
206+
<input class="btn btn-success" type="submit" value="Run" />
207+
</nav>
208+
</form>
209+
</section>
193210
</div>
194211
</section>
195212
{% endblock %}

api/base/settings/defaults.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@
321321

322322
# django-elasticsearch-metrics
323323
DJELME_BACKENDS = {
324+
'osfmetrics_es6': {
325+
'elasticsearch_metrics.imps.elastic6': {
326+
'hosts': osf_settings.ELASTIC6_URI,
327+
'retry_on_timeout': True,
328+
},
329+
},
324330
'osfmetrics_es8': {
325331
'elasticsearch_metrics.imps.elastic8': {
326332
# passthru kwargs to elasticsearch8 connection constructor
@@ -338,6 +344,8 @@
338344
},
339345
}
340346
OSF_USAGEEVENT_EXPIRATION_DAYS = 90
347+
ELASTICSEARCH_METRICS_DATE_FORMAT = '%Y'
348+
MONTHLY_USAGE_REPORT_EPOCH = '2026-05' # cannot create monthly usage reports before this point
341349

342350
WAFFLE_CACHE_NAME = 'waffle_cache'
343351
STORAGE_USAGE_CACHE_NAME = 'storage_usage'

api/metrics/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,9 @@ def _do_es_request(self, django_request, djelme_backend_name, method, path):
136136
{'Content-Type': _content_type, 'Accept': 'application/json'}
137137
if _content_type else None
138138
)
139+
_perform_fn = getattr(_client, 'perform_request', None) or _client.transport.perform_request
139140
try:
140-
_response = _client.perform_request(
141+
_response = _perform_fn(
141142
method,
142143
f'/{path}',
143144
params=django_request.GET.dict(),
@@ -150,7 +151,7 @@ def _do_es_request(self, django_request, djelme_backend_name, method, path):
150151
content_type='text/plain; charset=utf-8',
151152
status=_api_error.status_code,
152153
)
153-
return JsonResponse(_response.body)
154+
return JsonResponse(_response if isinstance(_response, dict) else _response.body)
154155

155156
def _get_es_client(self, djelme_backend_name):
156157
try:

docker-compose.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ volumes:
1313
external: false
1414
elasticsearch8_data_vol:
1515
external: false
16+
elasticsearch6_data_vol:
17+
external: false
1618
rabbitmq_vol:
1719
external: false
1820
preprints_dist_vol:
@@ -83,6 +85,22 @@ services:
8385
retries: 30
8486
stdin_open: true
8587

88+
# Temporary: Remove when done with es6
89+
elasticsearch6:
90+
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.1
91+
environment:
92+
- ES_JAVA_OPTS=-Xms512m -Xmx512m # reduce memory usage
93+
ports:
94+
- 9201:9200
95+
volumes:
96+
- elasticsearch6_data_vol:/usr/share/elasticsearch/data
97+
healthcheck:
98+
start_period: 15s
99+
test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
100+
interval: 10s
101+
retries: 30
102+
stdin_open: true
103+
86104
postgres:
87105
image: postgres:15.4
88106
command:

0 commit comments

Comments
 (0)