Active flag removal: switched from using mark_inactive to delete calls

This commit is contained in:
Akita Noek
2016-03-10 13:58:00 -05:00
parent 1e7c71edfb
commit ba833d683e
18 changed files with 55 additions and 377 deletions

View File

@@ -7,7 +7,6 @@ import logging
import time import time
# Django # Django
from django.http import Http404
from django.conf import settings from django.conf import settings
from django.db import connection from django.db import connection
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@@ -415,9 +414,7 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
raise PermissionDenied() raise PermissionDenied()
if parent_key: if parent_key:
# sub object has a ForeignKey to the parent, so we can't remove it sub.delete()
# from the set, only mark it as inactive.
sub.mark_inactive()
else: else:
relationship.remove(sub) relationship.remove(sub)
@@ -457,17 +454,9 @@ class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
# somewhat lame that delete has to call it's own permissions check # somewhat lame that delete has to call it's own permissions check
obj = self.get_object() obj = self.get_object()
# FIXME: Why isn't the active check being caught earlier by RBAC?
if not getattr(obj, 'active', True):
raise Http404()
if not getattr(obj, 'is_active', True):
raise Http404()
if not request.user.can_access(self.model, 'delete', obj): if not request.user.can_access(self.model, 'delete', obj):
raise PermissionDenied() raise PermissionDenied()
if hasattr(obj, 'mark_inactive'): obj.delete()
obj.mark_inactive()
else:
raise NotImplementedError('destroy() not implemented yet for %s' % obj)
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView): class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView):

View File

@@ -1093,8 +1093,6 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
can_delete = request.user.can_access(User, 'delete', obj) can_delete = request.user.can_access(User, 'delete', obj)
if not can_delete: if not can_delete:
raise PermissionDenied('Cannot delete user') raise PermissionDenied('Cannot delete user')
for own_credential in Credential.objects.filter(user=obj):
own_credential.mark_inactive()
return super(UserDetail, self).destroy(request, *args, **kwargs) return super(UserDetail, self).destroy(request, *args, **kwargs)
class UserAccessList(ResourceAccessList): class UserAccessList(ResourceAccessList):
@@ -1400,7 +1398,7 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
if sub_id is not None: if sub_id is not None:
return super(GroupChildrenList, self).unattach(request, *args, **kwargs) return super(GroupChildrenList, self).unattach(request, *args, **kwargs)
parent = self.get_parent_object() parent = self.get_parent_object()
parent.mark_inactive() parent.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
def _unattach(self, request, *args, **kwargs): # FIXME: Disabled for now for UI support. def _unattach(self, request, *args, **kwargs): # FIXME: Disabled for now for UI support.
@@ -1424,7 +1422,7 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
raise PermissionDenied() raise PermissionDenied()
if sub.parents.filter(active=True).exclude(pk=parent.pk).count() == 0: if sub.parents.filter(active=True).exclude(pk=parent.pk).count() == 0:
sub.mark_inactive() sub.delete()
else: else:
relationship.remove(sub) relationship.remove(sub)
@@ -1526,15 +1524,9 @@ class GroupDetail(RetrieveUpdateDestroyAPIView):
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
obj = self.get_object() obj = self.get_object()
# FIXME: Why isn't the active check being caught earlier by RBAC?
if not getattr(obj, 'active', True):
raise Http404()
if not getattr(obj, 'is_active', True):
raise Http404()
if not request.user.can_access(self.model, 'delete', obj): if not request.user.can_access(self.model, 'delete', obj):
raise PermissionDenied() raise PermissionDenied()
if hasattr(obj, 'mark_inactive'): obj.delete_recursive()
obj.mark_inactive_recursive()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class GroupAccessList(ResourceAccessList): class GroupAccessList(ResourceAccessList):

View File

@@ -53,13 +53,13 @@ class MemObject(object):
''' '''
Common code shared between in-memory groups and hosts. Common code shared between in-memory groups and hosts.
''' '''
def __init__(self, name, source_dir): def __init__(self, name, source_dir):
assert name, 'no name' assert name, 'no name'
assert source_dir, 'no source dir' assert source_dir, 'no source dir'
self.name = name self.name = name
self.source_dir = source_dir self.source_dir = source_dir
def load_vars(self, base_path): def load_vars(self, base_path):
all_vars = {} all_vars = {}
files_found = 0 files_found = 0
@@ -107,7 +107,7 @@ class MemGroup(MemObject):
group_vars = os.path.join(source_dir, 'group_vars', self.name) group_vars = os.path.join(source_dir, 'group_vars', self.name)
self.variables = self.load_vars(group_vars) self.variables = self.load_vars(group_vars)
logger.debug('Loaded group: %s', self.name) logger.debug('Loaded group: %s', self.name)
def child_group_by_name(self, name, loader): def child_group_by_name(self, name, loader):
if name == 'all': if name == 'all':
return return
@@ -266,7 +266,7 @@ class BaseLoader(object):
logger.debug('Filtering group %s', name) logger.debug('Filtering group %s', name)
return None return None
if name not in self.all_group.all_groups: if name not in self.all_group.all_groups:
group = MemGroup(name, self.source_dir) group = MemGroup(name, self.source_dir)
if not child: if not child:
all_group.add_child_group(group) all_group.add_child_group(group)
self.all_group.all_groups[name] = group self.all_group.all_groups[name] = group
@@ -315,7 +315,7 @@ class IniLoader(BaseLoader):
for t in tokens[1:]: for t in tokens[1:]:
k,v = t.split('=', 1) k,v = t.split('=', 1)
host.variables[k] = v host.variables[k] = v
group.add_host(host) group.add_host(host)
elif input_mode == 'children': elif input_mode == 'children':
group.child_group_by_name(line, self) group.child_group_by_name(line, self)
elif input_mode == 'vars': elif input_mode == 'vars':
@@ -328,7 +328,7 @@ class IniLoader(BaseLoader):
# from API documentation: # from API documentation:
# #
# if called with --list, inventory outputs like so: # if called with --list, inventory outputs like so:
# #
# { # {
# "databases" : { # "databases" : {
# "hosts" : [ "host1.example.com", "host2.example.com" ], # "hosts" : [ "host1.example.com", "host2.example.com" ],
@@ -581,7 +581,7 @@ class Command(NoArgsCommand):
def _get_instance_id(self, from_dict, default=''): def _get_instance_id(self, from_dict, default=''):
''' '''
Retrieve the instance ID from the given dict of host variables. Retrieve the instance ID from the given dict of host variables.
The instance ID variable may be specified as 'foo.bar', in which case The instance ID variable may be specified as 'foo.bar', in which case
the lookup will traverse into nested dicts, equivalent to: the lookup will traverse into nested dicts, equivalent to:
@@ -765,7 +765,7 @@ class Command(NoArgsCommand):
del_pks = all_del_pks[offset:(offset + self._batch_size)] del_pks = all_del_pks[offset:(offset + self._batch_size)]
for host in hosts_qs.filter(pk__in=del_pks): for host in hosts_qs.filter(pk__in=del_pks):
host_name = host.name host_name = host.name
host.mark_inactive() host.delete()
self.logger.info('Deleted host "%s"', host_name) self.logger.info('Deleted host "%s"', host_name)
if settings.SQL_DEBUG: if settings.SQL_DEBUG:
self.logger.warning('host deletions took %d queries for %d hosts', self.logger.warning('host deletions took %d queries for %d hosts',
@@ -799,7 +799,8 @@ class Command(NoArgsCommand):
del_pks = all_del_pks[offset:(offset + self._batch_size)] del_pks = all_del_pks[offset:(offset + self._batch_size)]
for group in groups_qs.filter(pk__in=del_pks): for group in groups_qs.filter(pk__in=del_pks):
group_name = group.name group_name = group.name
group.mark_inactive(recompute=False) with ignore_inventory_computed_fields():
group.delete()
self.logger.info('Group "%s" deleted', group_name) self.logger.info('Group "%s" deleted', group_name)
if settings.SQL_DEBUG: if settings.SQL_DEBUG:
self.logger.warning('group deletions took %d queries for %d groups', self.logger.warning('group deletions took %d queries for %d groups',
@@ -1297,7 +1298,7 @@ class Command(NoArgsCommand):
except CommandError as e: except CommandError as e:
self.mark_license_failure(save=True) self.mark_license_failure(save=True)
raise e raise e
if self.inventory_source.group: if self.inventory_source.group:
inv_name = 'group "%s"' % (self.inventory_source.group.name) inv_name = 'group "%s"' % (self.inventory_source.group.name)
else: else:
@@ -1336,7 +1337,7 @@ class Command(NoArgsCommand):
self.inventory_update.result_traceback = tb self.inventory_update.result_traceback = tb
self.inventory_update.status = status self.inventory_update.status = status
self.inventory_update.save(update_fields=['status', 'result_traceback']) self.inventory_update.save(update_fields=['status', 'result_traceback'])
if exc and isinstance(exc, CommandError): if exc and isinstance(exc, CommandError):
sys.exit(1) sys.exit(1)
elif exc: elif exc:

View File

@@ -203,15 +203,6 @@ class PasswordFieldsModel(BaseModel):
def _password_field_allows_ask(self, field): def _password_field_allows_ask(self, field):
return False # Override in subclasses if needed. return False # Override in subclasses if needed.
def mark_inactive(self, save=True):
'''
When marking a password model inactive we'll clear sensitive fields
'''
for sensitive_field in self.PASSWORD_FIELDS:
setattr(self, sensitive_field, "")
self.save()
super(PasswordFieldsModel, self).mark_inactive(save=save)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
new_instance = not bool(self.pk) new_instance = not bool(self.pk)
# If update_fields has been specified, add our field names to it, # If update_fields has been specified, add our field names to it,

View File

@@ -26,7 +26,7 @@ from awx.main.models.jobs import Job
from awx.main.models.unified_jobs import * # noqa from awx.main.models.unified_jobs import * # noqa
from awx.main.models.mixins import ResourceMixin from awx.main.models.mixins import ResourceMixin
from awx.main.models.notifications import Notifier from awx.main.models.notifications import Notifier
from awx.main.utils import ignore_inventory_computed_fields, _inventory_updates from awx.main.utils import _inventory_updates
from awx.main.conf import tower_settings from awx.main.conf import tower_settings
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript'] __all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript']
@@ -120,18 +120,6 @@ class Inventory(CommonModel, ResourceMixin):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('api:inventory_detail', args=(self.pk,)) return reverse('api:inventory_detail', args=(self.pk,))
def mark_inactive(self, save=True):
'''
When marking inventory inactive, also mark hosts and groups inactive.
'''
with ignore_inventory_computed_fields():
for host in self.hosts.filter(active=True):
host.mark_inactive()
for group in self.groups.filter(active=True):
group.mark_inactive(recompute=False)
for inventory_source in self.inventory_sources.filter(active=True):
inventory_source.mark_inactive()
super(Inventory, self).mark_inactive(save=save)
variables_dict = VarsDictProperty('variables') variables_dict = VarsDictProperty('variables')
@@ -412,15 +400,6 @@ class Host(CommonModelNameNotUnique, ResourceMixin):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('api:host_detail', args=(self.pk,)) return reverse('api:host_detail', args=(self.pk,))
def mark_inactive(self, save=True, from_inventory_import=False, skip_active_check=False):
'''
When marking hosts inactive, remove all associations to related
inventory sources.
'''
super(Host, self).mark_inactive(save=save, skip_active_check=skip_active_check)
if not from_inventory_import:
self.inventory_sources.clear()
def update_computed_fields(self, update_inventory=True, update_groups=True): def update_computed_fields(self, update_inventory=True, update_groups=True):
''' '''
Update model fields that are computed from database relationships. Update model fields that are computed from database relationships.
@@ -575,11 +554,11 @@ class Group(CommonModelNameNotUnique, ResourceMixin):
return reverse('api:group_detail', args=(self.pk,)) return reverse('api:group_detail', args=(self.pk,))
@transaction.atomic @transaction.atomic
def mark_inactive_recursive(self): def delete_recursive(self):
from awx.main.tasks import bulk_inventory_element_delete
from awx.main.utils import ignore_inventory_computed_fields from awx.main.utils import ignore_inventory_computed_fields
from awx.main.signals import disable_activity_stream from awx.main.signals import disable_activity_stream
def mark_actual(): def mark_actual():
all_group_hosts = Group.hosts.through.objects.select_related("host", "group").filter(group__inventory=self.inventory) all_group_hosts = Group.hosts.through.objects.select_related("host", "group").filter(group__inventory=self.inventory)
group_hosts = {'groups': {}, 'hosts': {}} group_hosts = {'groups': {}, 'hosts': {}}
@@ -629,38 +608,13 @@ class Group(CommonModelNameNotUnique, ResourceMixin):
for direct_child in group_children[group]: for direct_child in group_children[group]:
linked_children.append((group, direct_child)) linked_children.append((group, direct_child))
marked_groups.append(group) marked_groups.append(group)
Group.objects.filter(id__in=marked_groups).update(active=False) Group.objects.filter(id__in=marked_groups).delete()
Host.objects.filter(id__in=marked_hosts).update(active=False) Host.objects.filter(id__in=marked_hosts).delete()
Group.parents.through.objects.filter(to_group__id__in=marked_groups) update_inventory_computed_fields.delay(self.inventory.id)
Group.hosts.through.objects.filter(group__id__in=marked_groups)
Group.inventory_sources.through.objects.filter(group__id__in=marked_groups).delete()
bulk_inventory_element_delete.delay(self.inventory.id, groups=marked_groups, hosts=marked_hosts)
with ignore_inventory_computed_fields(): with ignore_inventory_computed_fields():
with disable_activity_stream(): with disable_activity_stream():
mark_actual() mark_actual()
def mark_inactive(self, save=True, recompute=True, from_inventory_import=False, skip_active_check=False):
'''
When marking groups inactive, remove all associations to related
groups/hosts/inventory_sources.
'''
def mark_actual():
super(Group, self).mark_inactive(save=save, skip_active_check=skip_active_check)
self.inventory_source.mark_inactive(save=save)
self.inventory_sources.clear()
self.parents.clear()
self.children.clear()
self.hosts.clear()
i = self.inventory
if from_inventory_import:
super(Group, self).mark_inactive(save=save, skip_active_check=skip_active_check)
elif recompute:
with ignore_inventory_computed_fields():
mark_actual()
i.update_computed_fields()
else:
mark_actual()
def update_computed_fields(self): def update_computed_fields(self):
''' '''

View File

@@ -79,11 +79,6 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def mark_inactive(self, save=True):
for script in self.custom_inventory_scripts.all():
script.organization = None
script.save()
super(Organization, self).mark_inactive(save=save)
class Team(CommonModelNameNotUnique, ResourceMixin): class Team(CommonModelNameNotUnique, ResourceMixin):
@@ -135,14 +130,6 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('api:team_detail', args=(self.pk,)) return reverse('api:team_detail', args=(self.pk,))
def mark_inactive(self, save=True):
'''
When marking a team inactive we'll wipe out its credentials also
'''
for cred in self.credentials.all():
cred.mark_inactive()
super(Team, self).mark_inactive(save=save)
class Permission(CommonModelNameNotUnique): class Permission(CommonModelNameNotUnique):
''' '''
@@ -351,22 +338,6 @@ class AuthToken(BaseModel):
return self.key return self.key
# Add mark_inactive method to User model.
def user_mark_inactive(user, save=True):
'''Use instead of delete to rename and mark users inactive.'''
if user.is_active:
# Set timestamp to datetime.isoformat() but without the time zone
# offset to stay withint the 30 character username limit.
dtnow = tz_now()
deleted_ts = dtnow.strftime('%Y-%m-%dT%H:%M:%S.%f')
user.username = '_d_%s' % deleted_ts
user.is_active = False
if save:
user.save()
User.add_to_class('mark_inactive', user_mark_inactive)
# Add get_absolute_url method to User model if not present. # Add get_absolute_url method to User model if not present.
if not hasattr(User, 'get_absolute_url'): if not hasattr(User, 'get_absolute_url'):
def user_get_absolute_url(user): def user_get_absolute_url(user):

View File

@@ -210,17 +210,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
self.next_job_run = related_schedules[0].next_run self.next_job_run = related_schedules[0].next_run
self.save(update_fields=['next_schedule', 'next_job_run']) self.save(update_fields=['next_schedule', 'next_job_run'])
def mark_inactive(self, save=True):
'''
When marking a unified job template inactive, also mark its schedules
inactive.
'''
for schedule in self.schedules.filter(active=True):
schedule.mark_inactive()
schedule.enabled = False
schedule.save()
super(UnifiedJobTemplate, self).mark_inactive(save=save)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# If update_fields has been specified, add our field names to it, # If update_fields has been specified, add our field names to it,
# if it hasn't been specified, then we're just doing a normal save. # if it hasn't been specified, then we're just doing a normal save.

View File

@@ -265,7 +265,7 @@ def migrate_children_from_inactive_group_to_parent_groups(sender, **kwargs):
if inventory_source_pk: if inventory_source_pk:
try: try:
inventory_source = InventorySource.objects.get(pk=inventory_source_pk, active=True) inventory_source = InventorySource.objects.get(pk=inventory_source_pk, active=True)
inventory_source.mark_inactive() inventory_source.delete()
except InventorySource.DoesNotExist: except InventorySource.DoesNotExist:
pass pass
inventory_pk = getattr(instance, '_saved_inventory_pk', None) inventory_pk = getattr(instance, '_saved_inventory_pk', None)

View File

@@ -110,17 +110,6 @@ def run_administrative_checks(self):
tower_admin_emails, tower_admin_emails,
fail_silently=True) fail_silently=True)
@task()
def bulk_inventory_element_delete(inventory, hosts=[], groups=[]):
from awx.main.signals import disable_activity_stream
with ignore_inventory_computed_fields():
with disable_activity_stream():
for group in groups:
Group.objects.get(id=group).mark_inactive(skip_active_check=True)
for host in hosts:
Host.objects.get(id=host).mark_inactive(skip_active_check=True)
update_inventory_computed_fields(inventory)
@task(bind=True) @task(bind=True)
def tower_periodic_scheduler(self): def tower_periodic_scheduler(self):
def get_last_run(): def get_last_run():

View File

@@ -637,8 +637,8 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
# Verify that the credential and inventory are null when they have # Verify that the credential and inventory are null when they have
# been deleted, can delete an ad hoc command without inventory or # been deleted, can delete an ad hoc command without inventory or
# credential. # credential.
self.credential.mark_inactive() self.credential.delete()
self.inventory.mark_inactive() self.inventory.delete()
with self.current_user('admin'): with self.current_user('admin'):
response = self.get(url, expect=200) response = self.get(url, expect=200)
self.assertEqual(response['credential'], None) self.assertEqual(response['credential'], None)
@@ -758,7 +758,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
tower_settings.AD_HOC_COMMANDS = ad_hoc_commands tower_settings.AD_HOC_COMMANDS = ad_hoc_commands
# Try to relaunch after the inventory has been marked inactive. # Try to relaunch after the inventory has been marked inactive.
self.inventory.mark_inactive() self.inventory.delete()
with self.current_user('admin'): with self.current_user('admin'):
response = self.get(url, expect=200) response = self.get(url, expect=200)
self.assertEqual(response['passwords_needed_to_start'], []) self.assertEqual(response['passwords_needed_to_start'], [])

View File

@@ -1,34 +0,0 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
# AWX
from awx.main.tests.base import BaseTest
from command_base import BaseCommandMixin
__all__ = ['AgeDeletedCommandFunctionalTest']
class AgeDeletedCommandFunctionalTest(BaseCommandMixin, BaseTest):
def setUp(self):
super(AgeDeletedCommandFunctionalTest, self).setUp()
self.create_test_license_file()
self.setup_instances()
self.setup_users()
self.organization = self.make_organization(self.super_django_user)
self.credential = self.make_credential()
self.credential2 = self.make_credential()
self.credential.mark_inactive(True)
self.credential2.mark_inactive(True)
self.credential_active = self.make_credential()
self.super_django_user.mark_inactive(True)
def test_default(self):
result, stdout, stderr = self.run_command('age_deleted')
self.assertEqual(stdout, 'Aged %d items\n' % 3)
def test_type(self):
result, stdout, stderr = self.run_command('age_deleted', type='Credential')
self.assertEqual(stdout, 'Aged %d items\n' % 2)
def test_id_type(self):
result, stdout, stderr = self.run_command('age_deleted', type='Credential', id=self.credential.pk)
self.assertEqual(stdout, 'Aged %d items\n' % 1)

View File

@@ -15,7 +15,6 @@ import unittest2 as unittest
# Django # Django
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.utils.timezone import now from django.utils.timezone import now
@@ -232,126 +231,6 @@ class DumpDataTest(BaseCommandMixin, BaseTest):
self.assertEqual(result, None) self.assertEqual(result, None)
json.loads(stdout) json.loads(stdout)
class CleanupDeletedTest(BaseCommandMixin, BaseTest):
'''
Test cases for cleanup_deleted management command.
'''
def setUp(self):
self.start_redis()
super(CleanupDeletedTest, self).setUp()
self.create_test_inventories()
def tearDown(self):
super(CleanupDeletedTest, self).tearDown()
self.stop_redis()
def get_model_counts(self):
def get_models(m):
if not m._meta.abstract:
yield m
for sub in m.__subclasses__():
for subm in get_models(sub):
yield subm
counts = {}
for model in get_models(PrimordialModel):
active = model.objects.filter(active=True).count()
inactive = model.objects.filter(active=False).count()
counts[model] = (active, inactive)
return counts
def test_cleanup_our_models(self):
# Test with nothing to be deleted.
counts_before = self.get_model_counts()
self.assertFalse(sum(x[1] for x in counts_before.values()))
result, stdout, stderr = self.run_command('cleanup_deleted')
self.assertEqual(result, None)
counts_after = self.get_model_counts()
self.assertEqual(counts_before, counts_after)
# "Delete" some hosts.
for host in Host.objects.all():
host.mark_inactive()
# With no parameters, "days" defaults to 90, which won't cleanup any of
# the hosts we just removed.
counts_before = self.get_model_counts()
self.assertTrue(sum(x[1] for x in counts_before.values()))
result, stdout, stderr = self.run_command('cleanup_deleted')
self.assertEqual(result, None)
counts_after = self.get_model_counts()
self.assertEqual(counts_before, counts_after)
# Even with days=1, the hosts will remain.
counts_before = self.get_model_counts()
self.assertTrue(sum(x[1] for x in counts_before.values()))
result, stdout, stderr = self.run_command('cleanup_deleted', days=1)
self.assertEqual(result, None)
counts_after = self.get_model_counts()
self.assertEqual(counts_before, counts_after)
# With days=0, the hosts will be deleted.
counts_before = self.get_model_counts()
self.assertTrue(sum(x[1] for x in counts_before.values()))
result, stdout, stderr = self.run_command('cleanup_deleted', days=0)
self.assertEqual(result, None)
counts_after = self.get_model_counts()
self.assertNotEqual(counts_before, counts_after)
self.assertFalse(sum(x[1] for x in counts_after.values()))
return # Don't test how long it takes (for now).
# Create lots of hosts already marked as deleted.
t = time.time()
dtnow = now()
for x in xrange(1000):
hostname = "_deleted_%s_host-%d" % (dtnow.isoformat(), x)
host = self.inventories[0].hosts.create(name=hostname, active=False)
create_elapsed = time.time() - t
# Time how long it takes to cleanup deleted items, should be no more
# then the time taken to create them.
counts_before = self.get_model_counts()
self.assertTrue(sum(x[1] for x in counts_before.values()))
t = time.time()
result, stdout, stderr = self.run_command('cleanup_deleted', days=0)
cleanup_elapsed = time.time() - t
self.assertEqual(result, None)
counts_after = self.get_model_counts()
self.assertNotEqual(counts_before, counts_after)
self.assertFalse(sum(x[1] for x in counts_after.values()))
self.assertTrue(cleanup_elapsed < create_elapsed,
'create took %0.3fs, cleanup took %0.3fs, expected < %0.3fs' % (create_elapsed, cleanup_elapsed, create_elapsed))
def get_user_counts(self):
active = User.objects.filter(is_active=True).count()
inactive = User.objects.filter(is_active=False).count()
return active, inactive
def test_cleanup_user_model(self):
# Test with nothing to be deleted.
counts_before = self.get_user_counts()
self.assertFalse(counts_before[1])
result, stdout, stderr = self.run_command('cleanup_deleted')
self.assertEqual(result, None)
counts_after = self.get_user_counts()
self.assertEqual(counts_before, counts_after)
# "Delete some users".
for user in User.objects.all():
user.mark_inactive()
self.assertTrue(len(user.username) <= 30,
'len(%r) == %d' % (user.username, len(user.username)))
# With days=1, no users will be deleted.
counts_before = self.get_user_counts()
self.assertTrue(counts_before[1])
result, stdout, stderr = self.run_command('cleanup_deleted', days=1)
self.assertEqual(result, None)
counts_after = self.get_user_counts()
self.assertEqual(counts_before, counts_after)
# With days=0, inactive users will be deleted.
counts_before = self.get_user_counts()
self.assertTrue(counts_before[1])
result, stdout, stderr = self.run_command('cleanup_deleted', days=0)
self.assertEqual(result, None)
counts_after = self.get_user_counts()
self.assertNotEqual(counts_before, counts_after)
self.assertFalse(counts_after[1])
@override_settings(CELERY_ALWAYS_EAGER=True, @override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
ANSIBLE_TRANSPORT='local') ANSIBLE_TRANSPORT='local')

View File

@@ -423,7 +423,7 @@ class InventoryTest(BaseTest):
del_children_url = reverse('api:group_children_list', args=(del_group.pk,)) del_children_url = reverse('api:group_children_list', args=(del_group.pk,))
nondel_url = reverse('api:group_detail', nondel_url = reverse('api:group_detail',
args=(Group.objects.get(name='nondel').pk,)) args=(Group.objects.get(name='nondel').pk,))
del_group.mark_inactive() del_group.delete()
nondel_detail = self.get(nondel_url, expect=200, auth=self.get_normal_credentials()) nondel_detail = self.get(nondel_url, expect=200, auth=self.get_normal_credentials())
self.post(del_children_url, data=nondel_detail, expect=403, auth=self.get_normal_credentials()) self.post(del_children_url, data=nondel_detail, expect=403, auth=self.get_normal_credentials())
@@ -944,13 +944,10 @@ class InventoryTest(BaseTest):
# Mark group C inactive. Its child groups and hosts should now also be # Mark group C inactive. Its child groups and hosts should now also be
# attached to group A. Group D hosts should be unchanged. Group C # attached to group A. Group D hosts should be unchanged. Group C
# should also no longer have any group or host relationships. # should also no longer have any group or host relationships.
g_c.mark_inactive() g_c.delete()
self.assertTrue(g_d in g_a.children.all()) self.assertTrue(g_d in g_a.children.all())
self.assertTrue(h_c in g_a.hosts.all()) self.assertTrue(h_c in g_a.hosts.all())
self.assertFalse(h_d in g_a.hosts.all()) self.assertFalse(h_d in g_a.hosts.all())
self.assertFalse(g_c.parents.all())
self.assertFalse(g_c.children.all())
self.assertFalse(g_c.hosts.all())
def test_safe_delete_recursion(self): def test_safe_delete_recursion(self):
# First hierarchy # First hierarchy
@@ -989,11 +986,9 @@ class InventoryTest(BaseTest):
self.assertTrue(other_sub_group in sub_group.children.all()) self.assertTrue(other_sub_group in sub_group.children.all())
# Now recursively remove its parent and the reference from subgroup should remain # Now recursively remove its parent and the reference from subgroup should remain
other_top_group.mark_inactive_recursive() other_top_group.delete_recursive()
other_top_group = Group.objects.get(pk=other_top_group.pk)
self.assertTrue(s2 in sub_group.all_hosts.all()) self.assertTrue(s2 in sub_group.all_hosts.all())
self.assertTrue(other_sub_group in sub_group.children.all()) self.assertTrue(other_sub_group in sub_group.children.all())
self.assertFalse(other_top_group.active)
def test_group_parents_and_children(self): def test_group_parents_and_children(self):
# Test for various levels of group parent/child relations, with hosts, # Test for various levels of group parent/child relations, with hosts,
@@ -1173,7 +1168,7 @@ class InventoryTest(BaseTest):
# Delete recently added hosts and verify the count drops. # Delete recently added hosts and verify the count drops.
hostnames4 = list('defg') hostnames4 = list('defg')
for host in Host.objects.filter(name__in=hostnames4): for host in Host.objects.filter(name__in=hostnames4):
host.mark_inactive() host.delete()
with self.current_user(self.super_django_user): with self.current_user(self.super_django_user):
response = self.get(url) response = self.get(url)
for n, d in enumerate(reversed(response['hosts'])): for n, d in enumerate(reversed(response['hosts'])):

View File

@@ -96,7 +96,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_explicit(self): def test_credential_explicit(self):
# Explicit, credential # Explicit, credential
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.cred_sue.mark_inactive() self.cred_sue.delete()
response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=202) response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=202)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')
@@ -105,7 +105,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_credential_explicit_via_credential_id(self): def test_credential_explicit_via_credential_id(self):
# Explicit, credential # Explicit, credential
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.cred_sue.mark_inactive() self.cred_sue.delete()
response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=202) response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=202)
j = Job.objects.get(pk=response['job']) j = Job.objects.get(pk=response['job'])
self.assertEqual(j.status, 'new') self.assertEqual(j.status, 'new')
@@ -131,15 +131,16 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
# Can't launch a job template without a credential defined (or if we # Can't launch a job template without a credential defined (or if we
# pass an invalid/inactive credential value). # pass an invalid/inactive credential value).
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.cred_sue.mark_inactive() self.cred_sue.delete()
self.post(self.launch_url, {}, expect=400) self.post(self.launch_url, {}, expect=400)
self.post(self.launch_url, {'credential': 0}, expect=400) self.post(self.launch_url, {'credential': 0}, expect=400)
self.post(self.launch_url, {'credential_id': 0}, expect=400) self.post(self.launch_url, {'credential_id': 0}, expect=400)
self.post(self.launch_url, {'credential': 'one'}, expect=400) self.post(self.launch_url, {'credential': 'one'}, expect=400)
self.post(self.launch_url, {'credential_id': 'one'}, expect=400) self.post(self.launch_url, {'credential_id': 'one'}, expect=400)
self.cred_doug.mark_inactive() doug_pk = self.cred_doug.pk
self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=400) self.cred_doug.delete()
self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=400) self.post(self.launch_url, {'credential': cred_doug_pk}, expect=400)
self.post(self.launch_url, {'credential_id': cred_doug_pk}, expect=400)
def test_explicit_unowned_cred(self): def test_explicit_unowned_cred(self):
# Explicitly specify a credential that we don't have access to # Explicitly specify a credential that we don't have access to
@@ -174,7 +175,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def test_deleted_credential_fail(self): def test_deleted_credential_fail(self):
# Job Templates with deleted credentials cannot be launched. # Job Templates with deleted credentials cannot be launched.
self.cred_sue.mark_inactive() self.cred_sue.delete()
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.post(self.launch_url, {}, expect=400) self.post(self.launch_url, {}, expect=400)
@@ -202,7 +203,7 @@ class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TransactionTe
passwords_required = ['ssh_password', 'become_password', 'ssh_key_unlock'] passwords_required = ['ssh_password', 'become_password', 'ssh_key_unlock']
# Job Templates with deleted credentials cannot be launched. # Job Templates with deleted credentials cannot be launched.
with self.current_user(self.user_sue): with self.current_user(self.user_sue):
self.cred_sue_ask.mark_inactive() self.cred_sue_ask.delete()
response = self.post(self.launch_url, {'credential_id': self.cred_sue_ask_many.pk}, expect=400) response = self.post(self.launch_url, {'credential_id': self.cred_sue_ask_many.pk}, expect=400)
for p in passwords_required: for p in passwords_required:
self.assertIn(p, response['passwords_needed_to_start']) self.assertIn(p, response['passwords_needed_to_start'])

View File

@@ -169,7 +169,7 @@ class ProjectsTest(BaseTransactionTest):
local_path = project.local_path local_path = project.local_path
response = self.get(url, expect=200, auth=self.get_super_credentials()) response = self.get(url, expect=200, auth=self.get_super_credentials())
self.assertTrue(local_path not in response['project_local_paths']) self.assertTrue(local_path not in response['project_local_paths'])
project.mark_inactive() project.delete()
response = self.get(url, expect=200, auth=self.get_super_credentials()) response = self.get(url, expect=200, auth=self.get_super_credentials())
self.assertTrue(local_path in response['project_local_paths']) self.assertTrue(local_path in response['project_local_paths'])

View File

@@ -88,7 +88,8 @@ class InventoryScriptTest(BaseScriptTest):
inventory=inventory, inventory=inventory,
variables=variables) variables=variables)
if x in (3, 7): if x in (3, 7):
host.mark_inactive() host.delete()
continue
hosts.append(host) hosts.append(host)
# add localhost just to make sure it's thrown into all (Ansible github bug) # add localhost just to make sure it's thrown into all (Ansible github bug)
@@ -106,7 +107,8 @@ class InventoryScriptTest(BaseScriptTest):
inventory=inventory, inventory=inventory,
variables=variables) variables=variables)
if x == 2: if x == 2:
group.mark_inactive() group.delete()
continue
groups.append(group) groups.append(group)
group.hosts.add(hosts[x]) group.hosts.add(hosts[x])
group.hosts.add(hosts[x + 5]) group.hosts.add(hosts[x + 5])
@@ -320,9 +322,9 @@ class InventoryScriptTest(BaseScriptTest):
def test_with_deleted_inventory(self): def test_with_deleted_inventory(self):
inventory = self.inventories[0] inventory = self.inventories[0]
inventory.mark_inactive() pk = inventory.pk
self.assertFalse(inventory.active) inventory.delete()
os.environ['INVENTORY_ID'] = str(inventory.pk) os.environ['INVENTORY_ID'] = str(pk)
rc, stdout, stderr = self.run_inventory_script(list=True) rc, stdout, stderr = self.run_inventory_script(list=True)
self.assertNotEqual(rc, 0, stderr) self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {'failed': True}) self.assertEqual(json.loads(stdout), {'failed': True})

View File

@@ -592,26 +592,8 @@ class RunJobTest(BaseJobExecutionTest):
new_group.children.remove(self.group) new_group.children.remove(self.group)
new_group = Group.objects.get(pk=new_group.pk) new_group = Group.objects.get(pk=new_group.pk)
self.assertFalse(new_group.has_active_failures) self.assertFalse(new_group.has_active_failures)
# Mark host inactive (should clear flag on parent group and inventory) # Delete host (should clear flag on parent group and inventory)
self.host.mark_inactive()
self.group = Group.objects.get(pk=self.group.pk)
self.assertFalse(self.group.has_active_failures)
self.inventory = Inventory.objects.get(pk=self.inventory.pk)
self.assertFalse(self.inventory.has_active_failures)
# Un-mark host as inactive (need to force update of flag on group and
# inventory)
host = self.host
host.name = '_'.join(host.name.split('_')[3:]) or 'undeleted host'
host.active = True
host.save()
host.update_computed_fields()
self.group = Group.objects.get(pk=self.group.pk)
self.assertTrue(self.group.has_active_failures)
self.inventory = Inventory.objects.get(pk=self.inventory.pk)
self.assertTrue(self.inventory.has_active_failures)
# Delete host. (should clear flag)
self.host.delete() self.host.delete()
self.host = None
self.group = Group.objects.get(pk=self.group.pk) self.group = Group.objects.get(pk=self.group.pk)
self.assertFalse(self.group.has_active_failures) self.assertFalse(self.group.has_active_failures)
self.inventory = Inventory.objects.get(pk=self.inventory.pk) self.inventory = Inventory.objects.get(pk=self.inventory.pk)
@@ -619,30 +601,7 @@ class RunJobTest(BaseJobExecutionTest):
def test_update_has_active_failures_when_job_removed(self): def test_update_has_active_failures_when_job_removed(self):
job = self.test_run_job_that_fails() job = self.test_run_job_that_fails()
# Mark job as inactive (should clear flags). # Delete (should clear flags).
job.mark_inactive()
self.host = Host.objects.get(pk=self.host.pk)
self.assertFalse(self.host.has_active_failures)
self.group = Group.objects.get(pk=self.group.pk)
self.assertFalse(self.group.has_active_failures)
self.inventory = Inventory.objects.get(pk=self.inventory.pk)
self.assertFalse(self.inventory.has_active_failures)
# Un-mark job as inactive (need to force update of flag)
job.active = True
job.save()
# Need to manually update last_job on host...
host = Host.objects.get(pk=self.host.pk)
host.last_job = job
host.last_job_host_summary = JobHostSummary.objects.get(job=job, host=host)
host.save()
self.inventory.update_computed_fields()
self.host = Host.objects.get(pk=self.host.pk)
self.assertTrue(self.host.has_active_failures)
self.group = Group.objects.get(pk=self.group.pk)
self.assertTrue(self.group.has_active_failures)
self.inventory = Inventory.objects.get(pk=self.inventory.pk)
self.assertTrue(self.inventory.has_active_failures)
# Delete job entirely.
job.delete() job.delete()
self.host = Host.objects.get(pk=self.host.pk) self.host = Host.objects.get(pk=self.host.pk)
self.assertFalse(self.host.has_active_failures) self.assertFalse(self.host.has_active_failures)
@@ -662,8 +621,8 @@ class RunJobTest(BaseJobExecutionTest):
self.host = Host.objects.get(pk=self.host.pk) self.host = Host.objects.get(pk=self.host.pk)
self.assertEqual(self.host.last_job, job1) self.assertEqual(self.host.last_job, job1)
self.assertEqual(self.host.last_job_host_summary.job, job1) self.assertEqual(self.host.last_job_host_summary.job, job1)
# Mark job1 inactive (should update host.last_job to None). # Delete job1 (should update host.last_job to None).
job1.mark_inactive() job1.delete()
self.host = Host.objects.get(pk=self.host.pk) self.host = Host.objects.get(pk=self.host.pk)
self.assertEqual(self.host.last_job, None) self.assertEqual(self.host.last_job, None)
self.assertEqual(self.host.last_job_host_summary, None) self.assertEqual(self.host.last_job_host_summary, None)

View File

@@ -196,7 +196,7 @@ class UsersTest(BaseTest):
self.post(url, expect=201, data=new_user2, auth=self.get_normal_credentials()) self.post(url, expect=201, data=new_user2, auth=self.get_normal_credentials())
self.post(url, expect=400, data=new_user2, auth=self.get_normal_credentials()) self.post(url, expect=400, data=new_user2, auth=self.get_normal_credentials())
# Normal user cannot add users after his org is marked inactive. # Normal user cannot add users after his org is marked inactive.
self.organizations[0].mark_inactive() self.organizations[0].delete()
new_user3 = dict(username='blippy3') new_user3 = dict(username='blippy3')
self.post(url, expect=403, data=new_user3, auth=self.get_normal_credentials()) self.post(url, expect=403, data=new_user3, auth=self.get_normal_credentials())
@@ -316,7 +316,7 @@ class UsersTest(BaseTest):
remote_addr=remote_addr) remote_addr=remote_addr)
# Token auth should be denied if the user is inactive. # Token auth should be denied if the user is inactive.
self.normal_django_user.mark_inactive() self.normal_django_user.delete()
response = self.get(user_me_url, expect=401, auth=auth_token2, response = self.get(user_me_url, expect=401, auth=auth_token2,
remote_addr=remote_addr) remote_addr=remote_addr)
self.assertEqual(response['detail'], 'User inactive or deleted') self.assertEqual(response['detail'], 'User inactive or deleted')
@@ -422,7 +422,7 @@ class UsersTest(BaseTest):
# Normal user can no longer see all users after the organization he # Normal user can no longer see all users after the organization he
# admins is marked inactive, nor can he see any other users that were # admins is marked inactive, nor can he see any other users that were
# in that org, so he only sees himself. # in that org, so he only sees himself.
self.organizations[0].mark_inactive() self.organizations[0].delete()
data3 = self.get(url, expect=200, auth=self.get_normal_credentials()) data3 = self.get(url, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data3['count'], 1) self.assertEquals(data3['count'], 1)