Skip to content

Commit

Permalink
Merge branch 'devel' into devel
Browse files Browse the repository at this point in the history
  • Loading branch information
sean-m-sullivan authored Dec 10, 2024
2 parents 83713ac + 325b6d3 commit 411a2af
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 72 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ jobs:
build-ui: false
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Run smoke test
run: ansible-playbook tools/docker-compose/ansible/smoke-test.yml -v
- name: Run live dev env tests
run: docker exec tools_awx_1 /bin/bash -c "make live_test"

awx-operator:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ test:
cd awxkit && $(VENV_BASE)/awx/bin/tox -re py3
awx-manage check_migrations --dry-run --check -n 'missing_migration_file'

live_test:
cd awx/main/tests/live && py.test tests/

## Run all API unit tests with coverage enabled.
test_coverage:
$(MAKE) test PYTEST_ARGS="--create-db --cov --cov-report=xml --junitxml=reports/junit.xml"
Expand Down
42 changes: 42 additions & 0 deletions awx/main/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Test Environments

Several of the subfolders of `awx/main/tests/` indicate a different required _environment_
where you can run the tests. Those folders are:

- `functional/` - requires a test database and no other services running
- `live/` - must run in `tools_awx_1` container launched by `make docker-compose`
- `unit/` - does not require a test database or any active services

### Functional and unit test environment

The functional and unit tests have an invocation in `make test`,
and this attaches several other things like schema that piggybacks on requests.
These tests are ran from the root AWX folder.

#### Functional tests

Only tests in the `functional/` folder should use the `@pytest.mark.django_db` decorator.
This is the only difference between the functional and unit folders,
the test environment is otherwise the same for both.

Functional tests use a sqlite3 database, so the postgres service is not necessary.

### Live tests

The live tests have an invocation in `make live_test` which will change
directory before running, which is required to pick up a different pytest
configuration.

This will use the postges container from `make docker-compose` for the database,
and will disable the pytest-django features of running with a test database
and running tests in transactions.
This means that any changes done in the course of the test could potentially
be seen in your browser via the API or UI, and anything the test fails
to clean up will remain in the database.

### Folders that should not contain tests

- `data/` - just files other tests use
- `docs/` - utilities for schema generation
- `factories/` - general utilities
- `manual/` - python files to be ran directly
3 changes: 3 additions & 0 deletions awx/main/tests/live/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This file is needed to undo the pytest settings from the project root
[pytest]
addopts = -p no:django -p awx.main.tests.live.pytest_django_config
12 changes: 12 additions & 0 deletions awx/main/tests/live/pytest_django_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import django

from awx import prepare_env


def pytest_load_initial_conftests(args):
"""Replacement for same-named method in pytest_django plugin
Instead of setting up a test database, this just sets up Django normally
this will give access to the postgres database as-is, for better and worse"""
prepare_env()
django.setup()
28 changes: 28 additions & 0 deletions awx/main/tests/live/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import time

# These tests are invoked from the awx/main/tests/live/ subfolder
# so any fixtures from higher-up conftest files must be explicitly included
from awx.main.tests.functional.conftest import * # noqa


def wait_to_leave_status(job, status, timeout=25, sleep_time=0.1):
"""Wait until the job does NOT have the specified status with some timeout
the default timeout of 25 if chosen because the task manager runs on a 20 second
schedule, and the API does not guarentee working jobs faster than this
"""
start = time.time()
while time.time() - start < timeout:
job.refresh_from_db()
if job.status != status:
return
time.sleep(sleep_time)
raise RuntimeError(f'Job failed to exit {status} in {timeout} seconds. job_explanation={job.job_explanation} tb={job.result_traceback}')


def wait_for_job(job, final_status='successful', running_timeout=800):
wait_to_leave_status(job, 'pending')
wait_to_leave_status(job, 'waiting')
wait_to_leave_status(job, 'running', timeout=running_timeout)

assert job.status == final_status, f'Job was not successful id={job.id} status={job.status}'
15 changes: 15 additions & 0 deletions awx/main/tests/live/tests/test_demo_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from awx.api.versioning import reverse

from awx.main.models import JobTemplate, Job

from awx.main.tests.live.tests.conftest import wait_for_job


def test_launch_demo_jt(post, admin):
jt = JobTemplate.objects.get(name='Demo Job Template')

url = reverse('api:job_template_launch', kwargs={'pk': jt.id})

r = post(url=url, data={}, user=admin, expect=201)
job = Job.objects.get(pk=r.data['id'])
wait_for_job(job)
23 changes: 23 additions & 0 deletions awx/main/tests/live/tests/test_partitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import timedelta

from django.utils.timezone import now
from django.db import connection

from awx.main.utils.common import create_partition, table_exists


def test_table_when_it_exists():
with connection.cursor() as cursor:
assert table_exists(cursor, 'main_job')


def test_table_when_it_does_not_exists():
with connection.cursor() as cursor:
assert not table_exists(cursor, 'main_not_a_table_check')


def test_create_partition_race_condition(mocker):
mocker.patch('awx.main.utils.common.table_exists', return_value=False)

create_partition('main_jobevent', start=now() - timedelta(days=2))
create_partition('main_jobevent', start=now() - timedelta(days=2))
26 changes: 18 additions & 8 deletions awx/main/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,17 @@ def deepmerge(a, b):
return b


def table_exists(cursor, table_name):
cursor.execute(f"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '{table_name}');")
row = cursor.fetchone()
if row is not None:
for val in row: # should only have 1
if val is True:
logger.debug(f'Event partition table {table_name} already exists')
return True
return False


def create_partition(tblname, start=None):
"""Creates new partition table for events. By default it covers the current hour."""
if start is None:
Expand All @@ -1154,13 +1165,8 @@ def create_partition(tblname, start=None):
try:
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute(f"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '{tblname}_{partition_label}');")
row = cursor.fetchone()
if row is not None:
for val in row: # should only have 1
if val is True:
logger.debug(f'Event partition table {tblname}_{partition_label} already exists')
return
if table_exists(cursor, f"{tblname}_{partition_label}"):
return

cursor.execute(
f'CREATE TABLE {tblname}_{partition_label} (LIKE {tblname} INCLUDING DEFAULTS INCLUDING CONSTRAINTS); '
Expand All @@ -1172,9 +1178,11 @@ def create_partition(tblname, start=None):
cause = e.__cause__
if cause and hasattr(cause, 'sqlstate'):
sqlstate = cause.sqlstate
if sqlstate is None:
raise
sqlstate_cls = psycopg.errors.lookup(sqlstate)

if psycopg.errors.DuplicateTable == sqlstate_cls or psycopg.errors.UniqueViolation == sqlstate_cls:
if sqlstate_cls in (psycopg.errors.DuplicateTable, psycopg.errors.DuplicateObject, psycopg.errors.UniqueViolation):
logger.info(f'Caught known error due to partition creation race: {e}')
else:
logger.error('SQL Error state: {} - {}'.format(sqlstate, sqlstate_cls))
Expand All @@ -1183,6 +1191,8 @@ def create_partition(tblname, start=None):
cause = e.__cause__
if cause and hasattr(cause, 'sqlstate'):
sqlstate = cause.sqlstate
if sqlstate is None:
raise
sqlstate_str = psycopg.errors.lookup(sqlstate)
logger.error('SQL Error state: {} - {}'.format(sqlstate, sqlstate_str))
raise
Expand Down
60 changes: 0 additions & 60 deletions tools/docker-compose/ansible/smoke-test.yml

This file was deleted.

4 changes: 2 additions & 2 deletions tools/docker-compose/bootstrap_development.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ if [ ! -d "/awx_devel/awx/ui/build/awx" ]; then
cp /awx_devel/awx/ui/placeholder_index_awx.html /awx_devel/awx/ui/build/awx/index_awx.html
fi

if output=$(awx-manage createsuperuser --noinput --username=admin --email=admin@localhost 2> /dev/null); then
if output=$(ANSIBLE_REVERSE_RESOURCE_SYNC=false awx-manage createsuperuser --noinput --username=admin --email=admin@localhost 2> /dev/null); then
echo $output
fi
echo "Admin password: ${DJANGO_SUPERUSER_PASSWORD}"

awx-manage create_preload_data
ANSIBLE_REVERSE_RESOURCE_SYNC=false awx-manage create_preload_data
awx-manage register_default_execution_environments

awx-manage provision_instance --hostname="$(hostname)" --node_type="$MAIN_NODE_TYPE"
Expand Down

0 comments on commit 411a2af

Please sign in to comment.