mirror of
https://github.com/ansible/awx.git
synced 2026-03-27 13:55:04 -02:30
Compare commits
8 Commits
revert-136
...
avoid_reap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f83647600 | ||
|
|
6461ecc762 | ||
|
|
3e6e0463b9 | ||
|
|
ededc61a71 | ||
|
|
3747f5b097 | ||
|
|
790ccd984c | ||
|
|
5d0849d746 | ||
|
|
a6a9d3427c |
@@ -70,7 +70,7 @@ def reap_waiting(instance=None, status='failed', job_explanation=None, grace_per
|
|||||||
reap_job(j, status, job_explanation=job_explanation)
|
reap_job(j, status, job_explanation=job_explanation)
|
||||||
|
|
||||||
|
|
||||||
def reap(instance=None, status='failed', job_explanation=None, excluded_uuids=None):
|
def reap(instance=None, status='failed', job_explanation=None, excluded_uuids=None, ref_time=None):
|
||||||
"""
|
"""
|
||||||
Reap all jobs in running for this instance.
|
Reap all jobs in running for this instance.
|
||||||
"""
|
"""
|
||||||
@@ -80,7 +80,7 @@ def reap(instance=None, status='failed', job_explanation=None, excluded_uuids=No
|
|||||||
hostname = instance.hostname
|
hostname = instance.hostname
|
||||||
workflow_ctype_id = ContentType.objects.get_for_model(WorkflowJob).id
|
workflow_ctype_id = ContentType.objects.get_for_model(WorkflowJob).id
|
||||||
jobs = UnifiedJob.objects.filter(
|
jobs = UnifiedJob.objects.filter(
|
||||||
Q(status='running') & (Q(execution_node=hostname) | Q(controller_node=hostname)) & ~Q(polymorphic_ctype_id=workflow_ctype_id)
|
Q(status='running', modified__lte=ref_time) & (Q(execution_node=hostname) | Q(controller_node=hostname)) & ~Q(polymorphic_ctype_id=workflow_ctype_id)
|
||||||
)
|
)
|
||||||
if excluded_uuids:
|
if excluded_uuids:
|
||||||
jobs = jobs.exclude(celery_task_id__in=excluded_uuids)
|
jobs = jobs.exclude(celery_task_id__in=excluded_uuids)
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ def cluster_node_heartbeat(dispatch_time=None, worker_tasks=None):
|
|||||||
active_task_ids = []
|
active_task_ids = []
|
||||||
for task_list in worker_tasks.values():
|
for task_list in worker_tasks.values():
|
||||||
active_task_ids.extend(task_list)
|
active_task_ids.extend(task_list)
|
||||||
reaper.reap(instance=this_inst, excluded_uuids=active_task_ids)
|
reaper.reap(instance=this_inst, excluded_uuids=active_task_ids, ref_time=datetime.fromisoformat(dispatch_time))
|
||||||
if max(len(task_list) for task_list in worker_tasks.values()) <= 1:
|
if max(len(task_list) for task_list in worker_tasks.values()) <= 1:
|
||||||
reaper.reap_waiting(instance=this_inst, excluded_uuids=active_task_ids, ref_time=datetime.fromisoformat(dispatch_time))
|
reaper.reap_waiting(instance=this_inst, excluded_uuids=active_task_ids, ref_time=datetime.fromisoformat(dispatch_time))
|
||||||
|
|
||||||
|
|||||||
@@ -337,6 +337,8 @@ class TestTaskPublisher:
|
|||||||
|
|
||||||
|
|
||||||
yesterday = tz_now() - datetime.timedelta(days=1)
|
yesterday = tz_now() - datetime.timedelta(days=1)
|
||||||
|
minute = tz_now() - datetime.timedelta(seconds=120)
|
||||||
|
now = tz_now()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -345,8 +347,8 @@ class TestJobReaper(object):
|
|||||||
'status, execution_node, controller_node, modified, fail',
|
'status, execution_node, controller_node, modified, fail',
|
||||||
[
|
[
|
||||||
('running', '', '', None, False), # running, not assigned to the instance
|
('running', '', '', None, False), # running, not assigned to the instance
|
||||||
('running', 'awx', '', None, True), # running, has the instance as its execution_node
|
('running', 'awx', '', minute, True), # running, has the instance as its execution_node
|
||||||
('running', '', 'awx', None, True), # running, has the instance as its controller_node
|
('running', '', 'awx', minute, True), # running, has the instance as its controller_node
|
||||||
('waiting', '', '', None, False), # waiting, not assigned to the instance
|
('waiting', '', '', None, False), # waiting, not assigned to the instance
|
||||||
('waiting', 'awx', '', None, False), # waiting, was edited less than a minute ago
|
('waiting', 'awx', '', None, False), # waiting, was edited less than a minute ago
|
||||||
('waiting', '', 'awx', None, False), # waiting, was edited less than a minute ago
|
('waiting', '', 'awx', None, False), # waiting, was edited less than a minute ago
|
||||||
@@ -368,7 +370,7 @@ class TestJobReaper(object):
|
|||||||
# we have to edit the modification time _without_ calling save()
|
# we have to edit the modification time _without_ calling save()
|
||||||
# (because .save() overwrites it to _now_)
|
# (because .save() overwrites it to _now_)
|
||||||
Job.objects.filter(id=j.id).update(modified=modified)
|
Job.objects.filter(id=j.id).update(modified=modified)
|
||||||
reaper.reap(i)
|
reaper.reap(i, ref_time=now)
|
||||||
reaper.reap_waiting(i)
|
reaper.reap_waiting(i)
|
||||||
job = Job.objects.first()
|
job = Job.objects.first()
|
||||||
if fail:
|
if fail:
|
||||||
@@ -379,13 +381,15 @@ class TestJobReaper(object):
|
|||||||
assert job.status == status
|
assert job.status == status
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'excluded_uuids, fail',
|
'excluded_uuids, fail, modified',
|
||||||
[
|
[
|
||||||
(['abc123'], False),
|
(['abc123'], False, None),
|
||||||
([], True),
|
([], False, None),
|
||||||
|
([], True, minute),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_do_not_reap_excluded_uuids(self, excluded_uuids, fail):
|
def test_do_not_reap_excluded_uuids(self, excluded_uuids, fail, modified):
|
||||||
|
"""Modified Test to account for ref_time in reap()"""
|
||||||
i = Instance(hostname='awx')
|
i = Instance(hostname='awx')
|
||||||
i.save()
|
i.save()
|
||||||
j = Job(
|
j = Job(
|
||||||
@@ -396,10 +400,13 @@ class TestJobReaper(object):
|
|||||||
celery_task_id='abc123',
|
celery_task_id='abc123',
|
||||||
)
|
)
|
||||||
j.save()
|
j.save()
|
||||||
|
if modified:
|
||||||
|
Job.objects.filter(id=j.id).update(modified=modified)
|
||||||
|
|
||||||
# if the UUID is excluded, don't reap it
|
# if the UUID is excluded, don't reap it
|
||||||
reaper.reap(i, excluded_uuids=excluded_uuids)
|
reaper.reap(i, excluded_uuids=excluded_uuids, ref_time=now)
|
||||||
job = Job.objects.first()
|
job = Job.objects.first()
|
||||||
|
|
||||||
if fail:
|
if fail:
|
||||||
assert job.status == 'failed'
|
assert job.status == 'failed'
|
||||||
assert 'marked as failed' in job.job_explanation
|
assert 'marked as failed' in job.job_explanation
|
||||||
@@ -412,6 +419,6 @@ class TestJobReaper(object):
|
|||||||
i.save()
|
i.save()
|
||||||
j = WorkflowJob(status='running', execution_node='awx')
|
j = WorkflowJob(status='running', execution_node='awx')
|
||||||
j.save()
|
j.save()
|
||||||
reaper.reap(i)
|
reaper.reap(i, ref_time=now)
|
||||||
|
|
||||||
assert WorkflowJob.objects.first().status == 'running'
|
assert WorkflowJob.objects.first().status == 'running'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class DistinctParametrize(object):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
class SurveyVariableValidation:
|
class TestSurveyVariableValidation:
|
||||||
def test_survey_answers_as_string(self, job_template_factory):
|
def test_survey_answers_as_string(self, job_template_factory):
|
||||||
objects = job_template_factory('job-template-with-survey', survey=[{'variable': 'var1', 'type': 'text'}], persisted=False)
|
objects = job_template_factory('job-template-with-survey', survey=[{'variable': 'var1', 'type': 'text'}], persisted=False)
|
||||||
jt = objects.job_template
|
jt = objects.job_template
|
||||||
@@ -57,7 +57,7 @@ class SurveyVariableValidation:
|
|||||||
accepted, rejected, errors = obj.accept_or_ignore_variables({"a": 5})
|
accepted, rejected, errors = obj.accept_or_ignore_variables({"a": 5})
|
||||||
assert rejected == {"a": 5}
|
assert rejected == {"a": 5}
|
||||||
assert accepted == {}
|
assert accepted == {}
|
||||||
assert str(errors[0]) == "Value 5 for 'a' expected to be a string."
|
assert str(errors['variables_needed_to_start'][0]) == "Value 5 for 'a' expected to be a string."
|
||||||
|
|
||||||
def test_job_template_survey_default_variable_validation(self, job_template_factory):
|
def test_job_template_survey_default_variable_validation(self, job_template_factory):
|
||||||
objects = job_template_factory(
|
objects = job_template_factory(
|
||||||
@@ -88,7 +88,7 @@ class SurveyVariableValidation:
|
|||||||
|
|
||||||
obj.survey_enabled = True
|
obj.survey_enabled = True
|
||||||
accepted, _, errors = obj.accept_or_ignore_variables({"a": 2})
|
accepted, _, errors = obj.accept_or_ignore_variables({"a": 2})
|
||||||
assert accepted == {{"a": 2.0}}
|
assert accepted == {"a": 2.0}
|
||||||
assert not errors
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import { useField } from 'formik';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Split, SplitItem, Button, Modal } from '@patternfly/react-core';
|
import { Split, SplitItem, Button, Modal } from '@patternfly/react-core';
|
||||||
import { ExpandArrowsAltIcon } from '@patternfly/react-icons';
|
import { ExpandArrowsAltIcon } from '@patternfly/react-icons';
|
||||||
import { yamlToJson, jsonToYaml, isJsonString } from 'util/yaml';
|
import {
|
||||||
|
yamlToJson,
|
||||||
|
jsonToYaml,
|
||||||
|
isJsonString,
|
||||||
|
parseVariableField,
|
||||||
|
} from 'util/yaml';
|
||||||
import { CheckboxField } from '../FormField';
|
import { CheckboxField } from '../FormField';
|
||||||
import MultiButtonToggle from '../MultiButtonToggle';
|
import MultiButtonToggle from '../MultiButtonToggle';
|
||||||
import CodeEditor from './CodeEditor';
|
import CodeEditor from './CodeEditor';
|
||||||
@@ -37,36 +42,24 @@ function VariablesField({
|
|||||||
// track focus manually, because the Code Editor library doesn't wire
|
// track focus manually, because the Code Editor library doesn't wire
|
||||||
// into Formik completely
|
// into Formik completely
|
||||||
const [shouldValidate, setShouldValidate] = useState(false);
|
const [shouldValidate, setShouldValidate] = useState(false);
|
||||||
const [mode, setMode] = useState(initialMode || YAML_MODE);
|
|
||||||
const validate = useCallback(
|
const validate = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
if (!shouldValidate) {
|
if (!shouldValidate) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (mode === YAML_MODE) {
|
parseVariableField(value);
|
||||||
yamlToJson(value);
|
|
||||||
} else {
|
|
||||||
JSON.parse(value);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
[shouldValidate, mode]
|
[shouldValidate]
|
||||||
);
|
);
|
||||||
const [field, meta, helpers] = useField({ name, validate });
|
const [field, meta, helpers] = useField({ name, validate });
|
||||||
|
const [mode, setMode] = useState(() =>
|
||||||
useEffect(() => {
|
isJsonString(field.value) ? JSON_MODE : initialMode || YAML_MODE
|
||||||
if (isJsonString(field.value)) {
|
);
|
||||||
// mode's useState above couldn't be initialized to JSON_MODE because
|
|
||||||
// the field value had to be defined below it
|
|
||||||
setMode(JSON_MODE);
|
|
||||||
onModeChange(JSON_MODE);
|
|
||||||
helpers.setValue(JSON.stringify(JSON.parse(field.value), null, 2));
|
|
||||||
}
|
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable-next-line import/prefer-default-export */
|
|
||||||
export const JOB_TYPE_URL_SEGMENTS = {
|
export const JOB_TYPE_URL_SEGMENTS = {
|
||||||
job: 'playbook',
|
job: 'playbook',
|
||||||
project_update: 'project',
|
project_update: 'project',
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ function AWXLogin({ alt, isAuthenticated }) {
|
|||||||
>
|
>
|
||||||
{(formik) => (
|
{(formik) => (
|
||||||
<LoginForm
|
<LoginForm
|
||||||
|
autoComplete="off"
|
||||||
data-cy="login-form"
|
data-cy="login-form"
|
||||||
className={authError ? 'pf-m-error' : ''}
|
className={authError ? 'pf-m-error' : ''}
|
||||||
helperText={helperText}
|
helperText={helperText}
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ describe('<Login />', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.only('form has autocomplete off', async () => {
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<AWXLogin isAuthenticated={() => false} />);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('form[autoComplete="off"]').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('custom logo renders Brand component with correct src and alt', async () => {
|
test('custom logo renders Brand component with correct src and alt', async () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user