Skip to content

Commit

Permalink
Merge pull request #882 from hdoupe/compute-maintenance
Browse files Browse the repository at this point in the history
Merged #882
  • Loading branch information
hdoupe committed Apr 6, 2018
2 parents 915a96e + 56188ce commit 7d61780
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 15 deletions.
3 changes: 2 additions & 1 deletion webapp/apps/btax/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
from ..taxbrain.models import (SeparatedValuesField,
CommaSeparatedField)

from ..taxbrain.behaviors import Hostnameable


class BTaxSaveInputs(models.Model):
class BTaxSaveInputs(Hostnameable, models.Model):
"""
This model contains all the parameters for the tax model and the tax
result.
Expand Down
7 changes: 5 additions & 2 deletions webapp/apps/btax/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from ..taxbrain.helpers import (format_csv,
is_wildcard)
from ..taxbrain.views import denormalize, normalize
from .compute import DropqComputeBtax, MockComputeBtax, JobFailError
from .compute import DropqComputeBtax, MockComputeBtax, JobFailError, BTAX_WORKERS

from ..constants import (METTR_TOOLTIP, METR_TOOLTIP, COC_TOOLTIP, DPRC_TOOLTIP,
START_YEAR)
Expand Down Expand Up @@ -403,7 +403,10 @@ def output_detail(request, pk):
return render(request, 'btax/results.html', context)

else:

if not model.check_hostnames(BTAX_WORKERS):
print('bad hostname', model.jobs_not_ready, BTAX_WORKERS)
return render_to_response('taxbrain/failed.html',
context={'is_btax': True})
job_ids = model.job_ids
jobs_to_check = model.jobs_not_ready
if not jobs_to_check:
Expand Down
7 changes: 4 additions & 3 deletions webapp/apps/dynamic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

from ..taxbrain.models import (CommaSeparatedField, SeparatedValuesField,
TaxSaveInputs, OutputUrl)
from ..taxbrain.behaviors import Resultable, Fieldable, DataSourceable
from ..taxbrain.behaviors import (Resultable, Fieldable, DataSourceable,
Hostnameable)
from ..taxbrain import helpers as taxbrain_helpers
from ..taxbrain import param_formatters

Expand Down Expand Up @@ -61,7 +62,7 @@ class Meta:


class DynamicBehaviorSaveInputs(DataSourceable, Fieldable, Resultable,
models.Model):
Hostnameable, models.Model):
"""
This model contains all the parameters for the dynamic behavioral tax
model and the tax result.
Expand Down Expand Up @@ -147,7 +148,7 @@ class Meta:
)


class DynamicElasticitySaveInputs(DataSourceable, models.Model):
class DynamicElasticitySaveInputs(DataSourceable, Hostnameable, models.Model):
"""
This model contains all the parameters for the dynamic elasticity
wrt GDP dynamic macro model and tax result
Expand Down
9 changes: 7 additions & 2 deletions webapp/apps/dynamic/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from ..taxbrain.helpers import (taxcalc_results_to_tables,
convert_val)
from ..taxbrain.param_displayers import default_behavior
from ..taxbrain.compute import JobFailError, DROPQ_WORKERS
from .helpers import (default_parameters, job_submitted,
ogusa_results_to_tables, success_text,
failure_text, normalize, denormalize, strip_empty_lists,
Expand Down Expand Up @@ -650,7 +651,9 @@ def elastic_results(request, pk):
return render(request, 'dynamic/elasticity_results.html', context)

else:

if not model.check_hostnames(DROPQ_WORKERS):
print('bad hostname', model.jobs_not_ready, DROPQ_WORKERS)
raise render_to_response('taxbrain/failed.html')
job_ids = model.job_ids
jobs_to_check = model.jobs_not_ready
if not jobs_to_check:
Expand Down Expand Up @@ -839,7 +842,9 @@ def behavior_results(request, pk):
return render(request, 'taxbrain/results.html', context)

else:

if not model.check_hostnames(DROPQ_WORKERS):
print('bad hostname', model.jobs_not_ready, DROPQ_WORKERS)
raise render_to_response('taxbrain/failed.html')
job_ids = model.job_ids
jobs_to_check = model.jobs_not_ready
if not jobs_to_check:
Expand Down
21 changes: 21 additions & 0 deletions webapp/apps/taxbrain/behaviors.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,24 @@ def use_puf_not_cps(self):
return True
else:
return False


class Hostnameable(models.Model):
"""
Mix-in for providing functionality around hostnames
"""
class Meta:
abstract = True

def check_hostnames(self, current_hostnames):
"""
Make sure all hostnames are valid before posting to/getting data from
them
"""
if self.jobs_not_ready is None:
return True
for id in self.jobs_not_ready:
hn = id.split('#')[1]
if hn not in current_hostnames:
return False
return True
12 changes: 11 additions & 1 deletion webapp/apps/taxbrain/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import requests
import taxcalc
from requests.exceptions import Timeout, RequestException
from requests.exceptions import Timeout, RequestException, ConnectionError
from .helpers import arrange_totals_by_row
from ..constants import START_YEAR
import requests_mock
Expand Down Expand Up @@ -317,6 +317,16 @@ def remote_results_ready(self, theurl, params):
mock.register_uri('GET', '/dropq_query_result', text='FAIL')
return DropqCompute.remote_results_ready(self, theurl, params)

class MockFailedComputeOnOldHost(MockCompute):
"""
Simulate requesting results from a host IP that is no longer used. This
action should raise a `ConnectionError`
"""
def remote_results_ready(self, theurl, params):
print 'MockFailedComputeOnOldHost remote_results_ready', theurl, params
raise requests.ConnectionError()


class NodeDownCompute(MockCompute):

__slots__ = ('count', 'num_times_to_wait', 'switch')
Expand Down
5 changes: 3 additions & 2 deletions webapp/apps/taxbrain/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import helpers
import param_formatters

from behaviors import Resultable, Fieldable, DataSourceable
from behaviors import Resultable, Fieldable, DataSourceable, Hostnameable


# digit or true/false (case insensitive)
Expand Down Expand Up @@ -86,7 +86,8 @@ class ErrorMessageTaxCalculator(models.Model):
text = models.CharField(blank=True, null=False, max_length=4000)


class TaxSaveInputs(DataSourceable, Fieldable, Resultable, models.Model):
class TaxSaveInputs(DataSourceable, Fieldable, Resultable, Hostnameable,
models.Model):
"""
This model contains all the parameters for the tax model and the tax
result.
Expand Down
40 changes: 39 additions & 1 deletion webapp/apps/taxbrain/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from ..helpers import (expand_1D, expand_2D, expand_list, package_up_vars,
format_csv, arrange_totals_by_row, default_taxcalc_data)
from ..compute import (DropqCompute, MockCompute, MockFailedCompute,
NodeDownCompute)
NodeDownCompute, MockFailedComputeOnOldHost)
from ..views import get_result_context
import taxcalc
from taxcalc import Policy
Expand Down Expand Up @@ -815,3 +815,41 @@ def test_get_not_avail_page_renders(self, start_year, start_year_is_none):
for t in response.templates])
edit_exp = '/taxbrain/edit/{}/?start_year={}'.format(pk, start_year)
assert response.context['edit_href'] == edit_exp

def test_get_failed_sim_on_old_host(self, monkeypatch):
"""
Simulate retrieving results from a host that is no longer used. Prior
to fix this raised a `requests.ConnectionError`
"""
start_year = 2018
data = get_post_data(start_year)
data['first_year'] = start_year

result = do_micro_sim(CLIENT, data)
# get DB object
pk = result['pk']
url = OutputUrl.objects.filter(pk=pk)
assert len(url) == 1
model = url[0].unique_inputs
# swap out the job_ids to an IP address that is not
# used anymore,
model.job_ids = [
u'abc#1.1.1.1',
u'def#2.2.2.2',
u'ghi#3.3.3.3',
u'jkl#4.4.4.4',
]
# simulate having some unfinished jobs and no result
model.jobs_not_ready = model.job_ids[:2]
model.tax_result = None
model.save()

from ...taxbrain import views
monkeypatch.setattr(views, 'dropq_compute', MockFailedComputeOnOldHost())

# try to get the bad results page
results_url = '/taxbrain/{}/'.format(pk)
response = CLIENT.get(results_url)

assert any([t.name == 'taxbrain/failed.html'
for t in response.templates])
7 changes: 5 additions & 2 deletions webapp/apps/taxbrain/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import tempfile
import re
import traceback
import requests


#Mock some module for imports because we can't fit them on Heroku slugs
Expand Down Expand Up @@ -44,7 +45,7 @@
)
from .param_displayers import nested_form_parameters
from .compute import (DropqCompute, MockCompute, JobFailError, NUM_BUDGET_YEARS,
NUM_BUDGET_YEARS_QUICK)
NUM_BUDGET_YEARS_QUICK, DROPQ_WORKERS)

dropq_compute = DropqCompute()

Expand Down Expand Up @@ -780,7 +781,9 @@ def output_detail(request, pk):
elif model.error_text:
return render(request, 'taxbrain/failed.html', {"error_msg": model.error_text.text})
else:

if not model.check_hostnames(DROPQ_WORKERS):
print('bad hostname', model.jobs_not_ready, DROPQ_WORKERS)
return render_to_response('taxbrain/failed.html')
job_ids = model.job_ids
jobs_to_check = model.jobs_not_ready
if not jobs_to_check:
Expand Down
49 changes: 48 additions & 1 deletion webapp/apps/test_assets/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
from datetime import datetime

from ..taxbrain.models import (JSONReformTaxCalculator,
OutputUrl)
OutputUrl, TaxSaveInputs)
from ..dynamic.models import (DynamicBehaviorSaveInputs,
DynamicElasticitySaveInputs)
from ..btax.models import BTaxSaveInputs
from utils import get_taxbrain_model, stringify_fields
from ..taxbrain.forms import TaxBrainForm

Expand Down Expand Up @@ -76,3 +79,47 @@ def parse_fields(self, start_year, fields, Form=TaxBrainForm,
# do some kind of check here using `exp_result`

return model

@pytest.mark.django_db
class TestHostNames:

@pytest.mark.parametrize(
'Inputs',
[TaxSaveInputs, DynamicBehaviorSaveInputs, DynamicElasticitySaveInputs,
BTaxSaveInputs]
)
def test_check_hostnames(self, Inputs):
inputs = Inputs()
inputs.job_ids = []
inputs.job_ids = [
u'abc#1.1.1.1',
u'def#2.2.2.2',
u'ghi#3.3.3.3',
u'jkl#4.4.4.4',
]
inputs.jobs_not_ready = inputs.job_ids[:2]
inputs.save()
assert inputs.check_hostnames(['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4'])


@pytest.mark.parametrize(
'Inputs',
[TaxSaveInputs, DynamicBehaviorSaveInputs, DynamicElasticitySaveInputs,
BTaxSaveInputs]
)
def test_check_hostnames_false(self, Inputs):
inputs = Inputs()
inputs.job_ids = [
u'abc#1.1.1.1',
u'def#2.2.2.2',
u'ghi#3.3.3.3',
u'jkl#4.4.4.4',
]
inputs.jobs_not_ready = inputs.job_ids[:2]
inputs.save()
assert not inputs.check_hostnames(['5.5.5.5'])

def test_check_hostnames_none(self):
inputs = TaxSaveInputs()
inputs.jobs_not_ready = None
assert inputs.check_hostnames([])

0 comments on commit 7d61780

Please sign in to comment.