diff --git a/lib/main/management/commands/acom_inventory.py b/lib/main/management/commands/acom_inventory.py index d288df1db5..085219d3f2 100755 --- a/lib/main/management/commands/acom_inventory.py +++ b/lib/main/management/commands/acom_inventory.py @@ -95,7 +95,7 @@ class Command(NoArgsCommand): self.get_host(inventory, host, indent=indent) else: raise CommandError('Either --list or --host must be specified') - except CommandError: + except CommandError, e: # Always return an empty hash on stdout, even when an error occurs. self.stdout.write(json.dumps({})) raise @@ -103,15 +103,21 @@ class Command(NoArgsCommand): if __name__ == '__main__': # FIXME: The DJANGO_SETTINGS_MODULE environment variable *should* already # be set if this script is called from a celery task. - settings_module = os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lib.settings') + settings_module_name = os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lib.settings') # FIXME: Not particularly fond of this sys.path hack, but it is needed # when a celery task calls ansible-playbook and needs to execute this # script directly. try: - __import__(settings_module) + settings_parent_module = __import__(settings_module_name) except ImportError: top_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..') sys.path.insert(0, os.path.abspath(top_dir)) + settings_parent_module = __import__(settings_module_name) + settings_module = getattr(settings_parent_module, settings_module_name.split('.')[-1]) + # Use the ACOM_TEST_DATABASE_NAME environment variable to specify the test + # database name when called from unit tests. + if os.environ.get('ACOM_TEST_DATABASE_NAME', None): + settings_module.DATABASES['default']['NAME'] = os.environ['ACOM_TEST_DATABASE_NAME'] from django.core.management import execute_from_command_line argv = [sys.argv[0], 'acom_inventory'] + sys.argv[1:] execute_from_command_line(argv) diff --git a/lib/main/models/__init__.py b/lib/main/models/__init__.py index 77f34e27e2..fd14946cfd 100644 --- a/lib/main/models/__init__.py +++ b/lib/main/models/__init__.py @@ -608,8 +608,11 @@ class LaunchJob(CommonModel): from lib.main.tasks import run_launch_job launch_job_status = self.launch_job_statuses.create(name='Launch Job Status %s' % now().isoformat()) task_result = run_launch_job.delay(launch_job_status.pk) - launch_job_status.celery_task = TaskMeta.objects.get(task_id=task_result.task_id) - launch_job_status.save() + try: + launch_job_status.celery_task = TaskMeta.objects.get(task_id=task_result.task_id) + launch_job_status.save() + except TaskMeta.DoesNotExist: + pass return launch_job_status # project has one default playbook but really should have a list of playbooks and flags ... diff --git a/lib/main/tasks.py b/lib/main/tasks.py index b9ff2161d9..ac1b84c45e 100644 --- a/lib/main/tasks.py +++ b/lib/main/tasks.py @@ -18,11 +18,14 @@ import os import subprocess from celery import task +from django.conf import settings from lib.main.models import * @task(name='run_launch_job') def run_launch_job(launch_job_status_pk): launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status_pk) + launch_job_status.status = 'running' + launch_job_status.save() launch_job = launch_job_status.launch_job plugin_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'plugins', 'callback')) @@ -33,10 +36,20 @@ def run_launch_job(launch_job_status_pk): env['ACOM_LAUNCH_JOB_STATUS_ID'] = str(launch_job_status.pk) env['ACOM_INVENTORY_ID'] = str(launch_job.inventory.pk) env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir + if hasattr(settings, 'ANSIBLE_TRANSPORT'): + env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT') playbook = launch_job.project.default_playbook - cmdline = ['ansible-playbook', '-i', inventory_script, '-v'] - if False: # local mode - cmdline.extend(['-c', 'local']) + cmdline = ['ansible-playbook', '-i', inventory_script]#, '-v'] cmdline.append(playbook) - subprocess.check_call(cmdline, env=env) - # FIXME: Capture stdout/stderr + proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env) + stdout, stderr = proc.communicate() + # Reload from database before updating/saving. + launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status_pk) + if proc.returncode == 0: + launch_job_status.status = 'successful' + else: + launch_job_status.status = 'failed' + launch_job_status.result_stdout = stdout + launch_job_status.result_stderr = stderr + launch_job_status.save() diff --git a/lib/main/tests/base.py b/lib/main/tests/base.py index f3fbc780c4..ec24352808 100644 --- a/lib/main/tests/base.py +++ b/lib/main/tests/base.py @@ -24,10 +24,13 @@ from django.test.client import Client from lib.main.models import * -class BaseTest(django.test.TestCase): +class BaseTestMixin(object): + ''' + Mixin with shared code for use by all test cases. + ''' def setUp(self): - super(BaseTest, self).setUp() + super(BaseTestMixin, self).setUp() self.object_ctr = 0 def make_user(self, username, password, super_user=False): @@ -131,3 +134,13 @@ class BaseTest(django.test.TestCase): data = self.get(collection_url, expect=200, auth=auth) return [item['url'] for item in data['results']] +class BaseTest(BaseTestMixin, django.test.TestCase): + ''' + Base class for unit tests. + ''' + +class BaseTransactionTest(BaseTestMixin, django.test.TransactionTestCase): + ''' + Base class for tests requiring transactions (or where the test database + needs to be accessed by subprocesses). + ''' diff --git a/lib/main/tests/tasks.py b/lib/main/tests/tasks.py index 7695d731ac..ce65ba47e0 100644 --- a/lib/main/tests/tasks.py +++ b/lib/main/tests/tasks.py @@ -16,13 +16,72 @@ # along with Ansible Commander. If not, see . +import os +import tempfile +from django.conf import settings +from django.test.utils import override_settings from lib.main.models import * -from lib.main.tests.base import BaseTest +from lib.main.tests.base import BaseTransactionTest -class RunLaunchJobTest(BaseTest): +TEST_PLAYBOOK = '''- hosts: test-group + gather_facts: False + tasks: + - name: should pass + command: test 1 = 1 +''' + +@override_settings(CELERY_ALWAYS_EAGER=True, + CELERY_EAGER_PROPAGATES_EXCEPTIONS=True) +class BaseCeleryTest(BaseTransactionTest): + ''' + Base class for celery task tests. + ''' + +@override_settings(ANSIBLE_TRANSPORT='local') +class RunLaunchJobTest(BaseCeleryTest): ''' Test cases for run_launch_job celery task. ''' def setUp(self): - pass + super(RunLaunchJobTest, self).setUp() + self.setup_users() + self.organization = self.make_organizations(self.super_django_user, 1)[0] + self.project = self.make_projects(self.normal_django_user, 1)[0] + handle, self.test_playbook = tempfile.mkstemp(suffix='.yml', prefix='playbook-') + test_playbook_file = os.fdopen(handle, 'w') + test_playbook_file.write(TEST_PLAYBOOK) + test_playbook_file.close() + self.project.default_playbook = self.test_playbook + self.project.save() + self.organization.projects.add(self.project) + self.inventory = Inventory.objects.create(name='test-inventory', + description='description for test-inventory', + organization=self.organization) + self.host = self.inventory.hosts.create(name='host.example.com', + inventory=self.inventory) + self.group = self.inventory.groups.create(name='test-group', + inventory=self.inventory) + self.group.hosts.add(self.host) + self.launch_job = LaunchJob.objects.create(name='test-launch-job', + inventory=self.inventory, + project=self.project) + # Pass test database name in environment for use by the inventory script. + os.environ['ACOM_TEST_DATABASE_NAME'] = settings.DATABASES['default']['NAME'] + + def tearDown(self): + super(RunLaunchJobTest, self).tearDown() + os.environ.pop('ACOM_TEST_DATABASE_NAME', None) + os.remove(self.test_playbook) + + def test_run_launch_job(self): + launch_job_status = self.launch_job.start() + self.assertEqual(launch_job_status.status, 'pending') + launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status.pk) + self.assertEqual(launch_job_status.status, 'successful') + self.assertTrue(launch_job_status.result_stdout) + #print 'stdout:', launch_job_status.result_stdout + #print 'stderr:', launch_job_status.result_stderr + + # FIXME: Test with a task that fails. + # FIXME: Get callback working. diff --git a/lib/plugins/callback/acom_callback.py b/lib/plugins/callback/acom_callback.py index 11f307c27a..c25e600cea 100644 --- a/lib/plugins/callback/acom_callback.py +++ b/lib/plugins/callback/acom_callback.py @@ -22,8 +22,8 @@ class CallbackModule(object): ''' def _log_event(self, event, *args, **kwargs): - print '====', event, args, kwargs # FIXME: Push these events back to the server. + pass#print '====', event, args, kwargs def on_any(self, *args, **kwargs): pass diff --git a/lib/settings/defaults.py b/lib/settings/defaults.py index d3292439be..f672ee95dc 100644 --- a/lib/settings/defaults.py +++ b/lib/settings/defaults.py @@ -52,7 +52,9 @@ REST_FRAMEWORK = { DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'acom.sqlite3'), + 'NAME': os.path.join(BASE_DIR, 'acom.sqlite3'), + # Test database cannot be :memory: for celery/inventory tests to work. + 'TEST_NAME': os.path.join(BASE_DIR, 'acom_test.sqlite3'), } }