mirror of
https://github.com/ansible/awx.git
synced 2026-03-19 09:57:33 -02:30
Merge pull request #1607 from anoek/performance
RBAC insert performance improvements and various unit test performance improvements
This commit is contained in:
@@ -5,7 +5,6 @@ import json
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db.models.signals import (
|
from django.db.models.signals import (
|
||||||
pre_save,
|
|
||||||
post_save,
|
post_save,
|
||||||
post_delete,
|
post_delete,
|
||||||
)
|
)
|
||||||
@@ -118,9 +117,8 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
setattr(cls, '__implicit_role_fields', [])
|
setattr(cls, '__implicit_role_fields', [])
|
||||||
getattr(cls, '__implicit_role_fields').append(self)
|
getattr(cls, '__implicit_role_fields').append(self)
|
||||||
|
|
||||||
pre_save.connect(self._pre_save, cls, True, dispatch_uid='implicit-role-pre-save')
|
|
||||||
post_save.connect(self._post_save, cls, True, dispatch_uid='implicit-role-post-save')
|
post_save.connect(self._post_save, cls, True, dispatch_uid='implicit-role-post-save')
|
||||||
post_delete.connect(self._post_delete, cls, True)
|
post_delete.connect(self._post_delete, cls, True, dispatch_uid='implicit-role-post-delete')
|
||||||
add_lazy_relation(cls, self, "self", self.bind_m2m_changed)
|
add_lazy_relation(cls, self, "self", self.bind_m2m_changed)
|
||||||
|
|
||||||
def bind_m2m_changed(self, _self, _role_class, cls):
|
def bind_m2m_changed(self, _self, _role_class, cls):
|
||||||
@@ -175,39 +173,44 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
getattr(instance, self.name).parents.remove(getattr(obj, field_attr))
|
getattr(instance, self.name).parents.remove(getattr(obj, field_attr))
|
||||||
return _m2m_update
|
return _m2m_update
|
||||||
|
|
||||||
def _create_role_instance_if_not_exists(self, instance):
|
|
||||||
role = getattr(instance, self.name, None)
|
|
||||||
if role:
|
|
||||||
return
|
|
||||||
Role_ = get_current_apps().get_model('main', 'Role')
|
|
||||||
role = Role_.objects.create(
|
|
||||||
created=now(),
|
|
||||||
modified=now(),
|
|
||||||
role_field=self.name,
|
|
||||||
name=self.role_name,
|
|
||||||
description=self.role_description
|
|
||||||
)
|
|
||||||
setattr(instance, self.name, role)
|
|
||||||
|
|
||||||
def _patch_role_content_object(self, instance):
|
|
||||||
role = getattr(instance, self.name)
|
|
||||||
role.content_object = instance
|
|
||||||
role.save()
|
|
||||||
|
|
||||||
def _pre_save(self, instance, *args, **kwargs):
|
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
|
||||||
implicit_role_field._create_role_instance_if_not_exists(instance)
|
|
||||||
|
|
||||||
def _post_save(self, instance, created, *args, **kwargs):
|
def _post_save(self, instance, created, *args, **kwargs):
|
||||||
if created:
|
Role_ = get_current_apps().get_model('main', 'Role')
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
ContentType_ = get_current_apps().get_model('contenttypes', 'ContentType')
|
||||||
implicit_role_field._patch_role_content_object(instance)
|
ct_id = ContentType_.objects.get_for_model(instance).id
|
||||||
|
|
||||||
with batch_role_ancestor_rebuilding():
|
with batch_role_ancestor_rebuilding():
|
||||||
|
# Create any missing role objects
|
||||||
|
missing_roles = []
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
cur_role = getattr(instance, implicit_role_field.name)
|
cur_role = getattr(instance, implicit_role_field.name, None)
|
||||||
|
if cur_role is None:
|
||||||
|
missing_roles.append(
|
||||||
|
Role_(
|
||||||
|
created=now(),
|
||||||
|
modified=now(),
|
||||||
|
role_field=implicit_role_field.name,
|
||||||
|
name=implicit_role_field.role_name,
|
||||||
|
description=implicit_role_field.role_description,
|
||||||
|
content_type_id=ct_id,
|
||||||
|
object_id=instance.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if len(missing_roles) > 0:
|
||||||
|
Role_.objects.bulk_create(missing_roles)
|
||||||
|
updates = {}
|
||||||
|
role_ids = []
|
||||||
|
for role in Role_.objects.filter(content_type_id=ct_id, object_id=instance.id):
|
||||||
|
setattr(instance, role.role_field, role)
|
||||||
|
updates[role.role_field] = role.id
|
||||||
|
role_ids.append(role.id)
|
||||||
|
type(instance).objects.filter(pk=instance.pk).update(**updates)
|
||||||
|
Role_._simultaneous_ancestry_rebuild(role_ids)
|
||||||
|
|
||||||
|
# Update parentage if necessary
|
||||||
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
|
cur_role = getattr(instance, implicit_role_field.name)
|
||||||
original_parents = set(json.loads(cur_role.implicit_parents))
|
original_parents = set(json.loads(cur_role.implicit_parents))
|
||||||
new_parents = implicit_role_field._resolve_parent_roles(instance)
|
new_parents = implicit_role_field._resolve_parent_roles(instance)
|
||||||
cur_role.parents.remove(*list(original_parents - new_parents))
|
cur_role.parents.remove(*list(original_parents - new_parents))
|
||||||
cur_role.parents.add(*list(new_parents - original_parents))
|
cur_role.parents.add(*list(new_parents - original_parents))
|
||||||
new_parents_list = list(new_parents)
|
new_parents_list = list(new_parents)
|
||||||
@@ -246,9 +249,11 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
return parent_roles
|
return parent_roles
|
||||||
|
|
||||||
def _post_delete(self, instance, *args, **kwargs):
|
def _post_delete(self, instance, *args, **kwargs):
|
||||||
this_role = getattr(instance, self.name)
|
role_ids = []
|
||||||
children = [c for c in this_role.children.all()]
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
this_role.delete()
|
role_ids.append(getattr(instance, implicit_role_field.name + '_id'))
|
||||||
with batch_role_ancestor_rebuilding():
|
|
||||||
for child in children:
|
Role_ = get_current_apps().get_model('main', 'Role')
|
||||||
child.rebuild_role_ancestor_list()
|
child_ids = [x for x in Role_.parents.through.objects.filter(to_role_id__in=role_ids).distinct().values_list('from_role_id', flat=True)]
|
||||||
|
Role_.objects.filter(id__in=role_ids).delete()
|
||||||
|
Role_._simultaneous_ancestry_rebuild(child_ids)
|
||||||
|
|||||||
@@ -121,14 +121,6 @@ class Role(CommonModelNameNotUnique):
|
|||||||
|
|
||||||
Note that this method relies on any parents' ancestor list being correct.
|
Note that this method relies on any parents' ancestor list being correct.
|
||||||
'''
|
'''
|
||||||
global tls
|
|
||||||
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
|
||||||
|
|
||||||
if batch_role_rebuilding:
|
|
||||||
roles_needing_rebuilding = getattr(tls, 'roles_needing_rebuilding')
|
|
||||||
roles_needing_rebuilding.add(self.id)
|
|
||||||
return
|
|
||||||
|
|
||||||
Role._simultaneous_ancestry_rebuild([self.id])
|
Role._simultaneous_ancestry_rebuild([self.id])
|
||||||
|
|
||||||
|
|
||||||
@@ -216,6 +208,15 @@ class Role(CommonModelNameNotUnique):
|
|||||||
if len(role_ids_to_rebuild) == 0:
|
if len(role_ids_to_rebuild) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
global tls
|
||||||
|
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
||||||
|
|
||||||
|
if batch_role_rebuilding:
|
||||||
|
roles_needing_rebuilding = getattr(tls, 'roles_needing_rebuilding')
|
||||||
|
roles_needing_rebuilding.update(set(role_ids_to_rebuild))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
loop_ct = 0
|
loop_ct = 0
|
||||||
|
|
||||||
@@ -225,19 +226,10 @@ class Role(CommonModelNameNotUnique):
|
|||||||
'roles_table': Role._meta.db_table,
|
'roles_table': Role._meta.db_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def split_ids_for_sqlite(role_ids):
|
def split_ids_for_sqlite(role_ids):
|
||||||
for i in xrange(0, len(role_ids), 999):
|
for i in xrange(0, len(role_ids), 999):
|
||||||
yield role_ids[i:i + 999]
|
yield role_ids[i:i + 999]
|
||||||
|
|
||||||
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
|
|
||||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
|
||||||
cursor.execute('''
|
|
||||||
DELETE FROM %(ancestors_table)s
|
|
||||||
WHERE ancestor_id IN (%(ids)s)
|
|
||||||
''' % sql_params)
|
|
||||||
|
|
||||||
|
|
||||||
while role_ids_to_rebuild:
|
while role_ids_to_rebuild:
|
||||||
if loop_ct > 1000:
|
if loop_ct > 1000:
|
||||||
raise Exception('Ancestry role rebuilding error: infinite loop detected')
|
raise Exception('Ancestry role rebuilding error: infinite loop detected')
|
||||||
@@ -371,4 +363,3 @@ def get_roles_on_resource(resource, accessor):
|
|||||||
object_id=resource.id
|
object_id=resource.id
|
||||||
).values_list('role_field', flat=True)
|
).values_list('role_field', flat=True)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import pytest
|
|||||||
from awx.main.middleware import ActivityStreamMiddleware
|
from awx.main.middleware import ActivityStreamMiddleware
|
||||||
from awx.main.models.activity_stream import ActivityStream
|
from awx.main.models.activity_stream import ActivityStream
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
def mock_feature_enabled(feature, bypass_database=None):
|
def mock_feature_enabled(feature, bypass_database=None):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True), reason="Activity stream not enabled")
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_activity_stream_list(monkeypatch, organization, get, user):
|
def test_get_activity_stream_list(monkeypatch, organization, get, user):
|
||||||
@@ -16,6 +18,7 @@ def test_get_activity_stream_list(monkeypatch, organization, get, user):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True), reason="Activity stream not enabled")
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_basic_fields(monkeypatch, organization, get, user):
|
def test_basic_fields(monkeypatch, organization, get, user):
|
||||||
@@ -35,6 +38,7 @@ def test_basic_fields(monkeypatch, organization, get, user):
|
|||||||
assert 'organization' in response.data['summary_fields']
|
assert 'organization' in response.data['summary_fields']
|
||||||
assert response.data['summary_fields']['organization'][0]['name'] == 'test-org'
|
assert response.data['summary_fields']['organization'][0]['name'] == 'test-org'
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True), reason="Activity stream not enabled")
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_middleware_actor_added(monkeypatch, post, get, user):
|
def test_middleware_actor_added(monkeypatch, post, get, user):
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ def test_content(hosts, fact_scans, get, user, fact_ansible_json):
|
|||||||
(fact_known, response) = setup_common(hosts, fact_scans, get, user)
|
(fact_known, response) = setup_common(hosts, fact_scans, get, user)
|
||||||
|
|
||||||
assert fact_known.host_id == response.data['host']
|
assert fact_known.host_id == response.data['host']
|
||||||
assert fact_ansible_json == json.loads(response.data['facts'])
|
assert fact_ansible_json == (json.loads(response.data['facts']) if isinstance(response.data['facts'], unicode) else response.data['facts']) # TODO: Just make response.data['facts'] when we're only dealing with postgres, or if jsonfields ever fixes this bug
|
||||||
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
||||||
assert fact_known.module == response.data['module']
|
assert fact_known.module == response.data['module']
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ def _test_search_by_module(hosts, fact_scans, get, user, fact_json, module_name)
|
|||||||
}
|
}
|
||||||
(fact_known, response) = setup_common(hosts, fact_scans, get, user, module_name=module_name, get_params=params)
|
(fact_known, response) = setup_common(hosts, fact_scans, get, user, module_name=module_name, get_params=params)
|
||||||
|
|
||||||
assert fact_json == json.loads(response.data['facts'])
|
assert fact_json == (json.loads(response.data['facts']) if isinstance(response.data['facts'], unicode) else response.data['facts']) # TODO: Just make response.data['facts'] when we're only dealing with postgres, or if jsonfields ever fixes this bug
|
||||||
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
||||||
assert module_name == response.data['module']
|
assert module_name == response.data['module']
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ def organization_resource_creator(organization, user):
|
|||||||
inventory = organization.inventories.create(name="associated-inv %s" % i)
|
inventory = organization.inventories.create(name="associated-inv %s" % i)
|
||||||
for i in range(projects):
|
for i in range(projects):
|
||||||
organization.projects.create(name="test-proj %s" % i,
|
organization.projects.create(name="test-proj %s" % i,
|
||||||
description="test-proj-desc",
|
description="test-proj-desc")
|
||||||
scm_type="git",
|
|
||||||
scm_url="https://github.com/jlaska/ansible-playbooks")
|
|
||||||
# Mix up the inventories and projects used by the job templates
|
# Mix up the inventories and projects used by the job templates
|
||||||
i_proj = 0
|
i_proj = 0
|
||||||
i_inv = 0
|
i_inv = 0
|
||||||
|
|||||||
@@ -80,12 +80,11 @@ def test_process_facts_message_ansible_overwrite(fact_scans, fact_msg_ansible):
|
|||||||
|
|
||||||
fact_obj = Fact.objects.get(id=fact_returned.id)
|
fact_obj = Fact.objects.get(id=fact_returned.id)
|
||||||
assert key in fact_obj.facts
|
assert key in fact_obj.facts
|
||||||
assert json.loads(fact_obj.facts) == fact_msg_ansible['facts']
|
assert fact_msg_ansible['facts'] == (json.loads(fact_obj.facts) if isinstance(fact_obj.facts, unicode) else fact_obj.facts) # TODO: Just make response.data['facts'] when we're only dealing with postgres, or if jsonfields ever fixes this bug
|
||||||
assert value == json.loads(fact_obj.facts)[key]
|
|
||||||
|
|
||||||
# Ensure that the message flows from the socket through to process_fact_message()
|
# Ensure that the message flows from the socket through to process_fact_message()
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_run_receiver(mocker, fact_msg_ansible):
|
def test_run_receiver(mocker, fact_msg_ansible):
|
||||||
mocker.patch("awx.main.socket.Socket.listen", return_value=[fact_msg_ansible])
|
mocker.patch("awx.main.socket.Socket.listen", return_value=[fact_msg_ansible])
|
||||||
|
|
||||||
receiver = FactCacheReceiver()
|
receiver = FactCacheReceiver()
|
||||||
|
|||||||
@@ -106,8 +106,6 @@ def team_member(user, team):
|
|||||||
def project(instance, organization):
|
def project(instance, organization):
|
||||||
prj = Project.objects.create(name="test-proj",
|
prj = Project.objects.create(name="test-proj",
|
||||||
description="test-proj-desc",
|
description="test-proj-desc",
|
||||||
scm_type="git",
|
|
||||||
scm_url="https://github.com/jlaska/ansible-playbooks",
|
|
||||||
organization=organization
|
organization=organization
|
||||||
)
|
)
|
||||||
return prj
|
return prj
|
||||||
@@ -120,8 +118,6 @@ def project_factory(organization):
|
|||||||
except Project.DoesNotExist:
|
except Project.DoesNotExist:
|
||||||
prj = Project.objects.create(name=name,
|
prj = Project.objects.create(name=name,
|
||||||
description="description for " + name,
|
description="description for " + name,
|
||||||
scm_type="git",
|
|
||||||
scm_url="https://github.com/jlaska/ansible-playbooks",
|
|
||||||
organization=organization
|
organization=organization
|
||||||
)
|
)
|
||||||
return prj
|
return prj
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from awx.main.models.inventory import Host
|
from awx.main.models.inventory import Host
|
||||||
from awx.main.models.fact import Fact
|
from awx.main.models.fact import Fact
|
||||||
@@ -14,6 +15,7 @@ from awx.fact.models.fact import FactVersion, FactHost
|
|||||||
def micro_to_milli(micro):
|
def micro_to_milli(micro):
|
||||||
return micro - (((int)(micro / 1000)) * 1000)
|
return micro - (((int)(micro / 1000)) * 1000)
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'MONGO_DB', None), reason="MongoDB not configured")
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.mongo_db
|
@pytest.mark.mongo_db
|
||||||
def test_migrate_facts(inventories, hosts, hosts_mongo, fact_scans):
|
def test_migrate_facts(inventories, hosts, hosts_mongo, fact_scans):
|
||||||
@@ -27,7 +29,7 @@ def test_migrate_facts(inventories, hosts, hosts_mongo, fact_scans):
|
|||||||
assert migrated_count == 24
|
assert migrated_count == 24
|
||||||
assert not_migrated_count == 0
|
assert not_migrated_count == 0
|
||||||
|
|
||||||
|
|
||||||
for fact_mongo, fact_version in facts_known:
|
for fact_mongo, fact_version in facts_known:
|
||||||
host = Host.objects.get(inventory_id=fact_mongo.host.inventory_id, name=fact_mongo.host.hostname)
|
host = Host.objects.get(inventory_id=fact_mongo.host.inventory_id, name=fact_mongo.host.hostname)
|
||||||
t = fact_mongo.timestamp - datetime.timedelta(microseconds=micro_to_milli(fact_mongo.timestamp.microsecond))
|
t = fact_mongo.timestamp - datetime.timedelta(microseconds=micro_to_milli(fact_mongo.timestamp.microsecond))
|
||||||
@@ -36,6 +38,7 @@ def test_migrate_facts(inventories, hosts, hosts_mongo, fact_scans):
|
|||||||
assert len(fact) == 1
|
assert len(fact) == 1
|
||||||
assert fact[0] is not None
|
assert fact[0] is not None
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'MONGO_DB', None), reason="MongoDB not configured")
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.mongo_db
|
@pytest.mark.mongo_db
|
||||||
def test_migrate_facts_hostname_does_not_exist(inventories, hosts, hosts_mongo, fact_scans):
|
def test_migrate_facts_hostname_does_not_exist(inventories, hosts, hosts_mongo, fact_scans):
|
||||||
@@ -60,7 +63,8 @@ def test_migrate_facts_hostname_does_not_exist(inventories, hosts, hosts_mongo,
|
|||||||
|
|
||||||
assert len(fact) == 1
|
assert len(fact) == 1
|
||||||
assert fact[0] is not None
|
assert fact[0] is not None
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'MONGO_DB', None), reason="MongoDB not configured")
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.mongo_db
|
@pytest.mark.mongo_db
|
||||||
def test_drop_system_tracking_db(inventories, hosts, hosts_mongo, fact_scans):
|
def test_drop_system_tracking_db(inventories, hosts, hosts_mongo, fact_scans):
|
||||||
@@ -73,7 +77,7 @@ def test_drop_system_tracking_db(inventories, hosts, hosts_mongo, fact_scans):
|
|||||||
assert FactHost.objects.all().count() > 0
|
assert FactHost.objects.all().count() > 0
|
||||||
|
|
||||||
system_tracking.drop_system_tracking_db()
|
system_tracking.drop_system_tracking_db()
|
||||||
|
|
||||||
assert FactMongo.objects.all().count() == 0
|
assert FactMongo.objects.all().count() == 0
|
||||||
assert FactVersion.objects.all().count() == 0
|
assert FactVersion.objects.all().count() == 0
|
||||||
assert FactHost.objects.all().count() == 0
|
assert FactHost.objects.all().count() == 0
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def test_basic_parameterization(get, post, user, organization):
|
|||||||
response = post(url,
|
response = post(url,
|
||||||
dict(name="test-webhook",
|
dict(name="test-webhook",
|
||||||
description="test webhook",
|
description="test webhook",
|
||||||
organization=1,
|
organization=organization.id,
|
||||||
notification_type="webhook",
|
notification_type="webhook",
|
||||||
notification_configuration=dict(url="http://localhost",
|
notification_configuration=dict(url="http://localhost",
|
||||||
headers={"Test": "Header"})),
|
headers={"Test": "Header"})),
|
||||||
@@ -72,7 +72,7 @@ def test_inherited_notifiers(get, post, user, organization, project):
|
|||||||
response = post(url,
|
response = post(url,
|
||||||
dict(name="test-webhook-{}".format(nfiers),
|
dict(name="test-webhook-{}".format(nfiers),
|
||||||
description="test webhook {}".format(nfiers),
|
description="test webhook {}".format(nfiers),
|
||||||
organization=1,
|
organization=organization.id,
|
||||||
notification_type="webhook",
|
notification_type="webhook",
|
||||||
notification_configuration=dict(url="http://localhost",
|
notification_configuration=dict(url="http://localhost",
|
||||||
headers={"Test": "Header"})),
|
headers={"Test": "Header"})),
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import mock # noqa
|
import mock # noqa
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from awx.main.models import Project
|
from awx.main.models import Project
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Project listing and visibility tests
|
# Project listing and visibility tests
|
||||||
#
|
#
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db
|
||||||
def test_user_project_list(get, project_factory, organization, admin, alice, bob):
|
def test_user_project_list(get, project_factory, organization, admin, alice, bob):
|
||||||
'List of projects a user has access to, filtered by projects you can also see'
|
'List of projects a user has access to, filtered by projects you can also see'
|
||||||
|
|
||||||
@@ -43,9 +43,7 @@ def test_user_project_list(get, project_factory, organization, admin, alice, bob
|
|||||||
assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2
|
assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
def setup_test_team_project_list(project_factory, team_factory, admin, alice, bob):
|
||||||
def test_team_project_list(get, project_factory, team_factory, admin, alice, bob):
|
|
||||||
'List of projects a team has access to, filtered by projects you can also see'
|
|
||||||
team1 = team_factory('team1')
|
team1 = team_factory('team1')
|
||||||
team2 = team_factory('team2')
|
team2 = team_factory('team2')
|
||||||
|
|
||||||
@@ -61,6 +59,12 @@ def test_team_project_list(get, project_factory, team_factory, admin, alice, bob
|
|||||||
|
|
||||||
team1.member_role.members.add(alice)
|
team1.member_role.members.add(alice)
|
||||||
team2.member_role.members.add(bob)
|
team2.member_role.members.add(bob)
|
||||||
|
return team1, team2
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_team_project_list(get, project_factory, team_factory, admin, alice, bob):
|
||||||
|
'List of projects a team has access to, filtered by projects you can also see'
|
||||||
|
team1, team2 = setup_test_team_project_list(project_factory, team_factory, admin, alice, bob)
|
||||||
|
|
||||||
# admins can see all projects on a team
|
# admins can see all projects on a team
|
||||||
assert get(reverse('api:team_projects_list', args=(team1.pk,)), admin).data['count'] == 2
|
assert get(reverse('api:team_projects_list', args=(team1.pk,)), admin).data['count'] == 2
|
||||||
@@ -69,17 +73,16 @@ def test_team_project_list(get, project_factory, team_factory, admin, alice, bob
|
|||||||
# users can see all projects on teams they are a member of
|
# users can see all projects on teams they are a member of
|
||||||
assert get(reverse('api:team_projects_list', args=(team1.pk,)), alice).data['count'] == 2
|
assert get(reverse('api:team_projects_list', args=(team1.pk,)), alice).data['count'] == 2
|
||||||
|
|
||||||
# alice should not be able to see team2 projects because she doesn't have access to team2
|
|
||||||
res = get(reverse('api:team_projects_list', args=(team2.pk,)), alice)
|
|
||||||
assert res.status_code == 403
|
|
||||||
# but if she does, then she should only see the shared project
|
# but if she does, then she should only see the shared project
|
||||||
team2.auditor_role.members.add(alice)
|
team2.auditor_role.members.add(alice)
|
||||||
assert get(reverse('api:team_projects_list', args=(team2.pk,)), alice).data['count'] == 1
|
assert get(reverse('api:team_projects_list', args=(team2.pk,)), alice).data['count'] == 1
|
||||||
team2.auditor_role.members.remove(alice)
|
team2.auditor_role.members.remove(alice)
|
||||||
|
|
||||||
|
|
||||||
# Test user endpoints first, very similar tests to test_user_project_list
|
# Test user endpoints first, very similar tests to test_user_project_list
|
||||||
# but permissions are being derived from team membership instead.
|
# but permissions are being derived from team membership instead.
|
||||||
|
with transaction.atomic():
|
||||||
|
res = get(reverse('api:user_projects_list', args=(bob.pk,)), alice)
|
||||||
|
assert res.status_code == 403
|
||||||
|
|
||||||
# admins can see all projects
|
# admins can see all projects
|
||||||
assert get(reverse('api:user_projects_list', args=(admin.pk,)), admin).data['count'] == 3
|
assert get(reverse('api:user_projects_list', args=(admin.pk,)), admin).data['count'] == 3
|
||||||
@@ -91,28 +94,43 @@ def test_team_project_list(get, project_factory, team_factory, admin, alice, bob
|
|||||||
# users can see their own projects
|
# users can see their own projects
|
||||||
assert get(reverse('api:user_projects_list', args=(alice.pk,)), alice).data['count'] == 2
|
assert get(reverse('api:user_projects_list', args=(alice.pk,)), alice).data['count'] == 2
|
||||||
|
|
||||||
# alice should not be able to see bob
|
|
||||||
res = get(reverse('api:user_projects_list', args=(bob.pk,)), alice)
|
|
||||||
assert res.status_code == 403
|
|
||||||
|
|
||||||
# alice should see all projects they can see when viewing an admin
|
# alice should see all projects they can see when viewing an admin
|
||||||
assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2
|
assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_team_project_list_fail1(get, project_factory, team_factory, admin, alice, bob):
|
||||||
|
# alice should not be able to see team2 projects because she doesn't have access to team2
|
||||||
|
team1, team2 = setup_test_team_project_list(project_factory, team_factory, admin, alice, bob)
|
||||||
|
res = get(reverse('api:team_projects_list', args=(team2.pk,)), alice)
|
||||||
|
assert res.status_code == 403
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_team_project_list_fail2(get, project_factory, team_factory, admin, alice, bob):
|
||||||
|
team1, team2 = setup_test_team_project_list(project_factory, team_factory, admin, alice, bob)
|
||||||
|
# alice should not be able to see bob
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.parametrize("u,expected_status_code", [
|
||||||
def test_create_project(post, organization, org_admin, org_member, admin, rando):
|
('rando', 403),
|
||||||
test_list = [rando, org_member, org_admin, admin]
|
('org_member', 403),
|
||||||
expected_status_codes = [403, 403, 201, 201]
|
('org_admin', 201),
|
||||||
|
('admin', 201)
|
||||||
|
])
|
||||||
|
@pytest.mark.django_db()
|
||||||
|
def test_create_project(post, organization, org_admin, org_member, admin, rando, u, expected_status_code):
|
||||||
|
if u == 'rando':
|
||||||
|
u = rando
|
||||||
|
elif u == 'org_member':
|
||||||
|
u = org_member
|
||||||
|
elif u == 'org_admin':
|
||||||
|
u = org_admin
|
||||||
|
elif u == 'admin':
|
||||||
|
u = admin
|
||||||
|
|
||||||
for i, u in enumerate(test_list):
|
result = post(reverse('api:project_list'), {
|
||||||
result = post(reverse('api:project_list'), {
|
'name': 'Project',
|
||||||
'name': 'Project %d' % i,
|
'organization': organization.id,
|
||||||
'organization': organization.id,
|
}, u)
|
||||||
}, u)
|
print(result.data)
|
||||||
print(result.data)
|
assert result.status_code == expected_status_code
|
||||||
assert result.status_code == expected_status_codes[i]
|
if expected_status_code == 201:
|
||||||
if expected_status_codes[i] == 201:
|
assert Project.objects.filter(name='Project', organization=organization).exists()
|
||||||
assert Project.objects.filter(name='Project %d' % i, organization=organization).exists()
|
|
||||||
else:
|
|
||||||
assert not Project.objects.filter(name='Project %d' % i, organization=organization).exists()
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import mock # noqa
|
import mock # noqa
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
|
from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
|
||||||
|
|
||||||
@@ -266,7 +267,7 @@ def test_remove_user_to_role(post, admin, role):
|
|||||||
post(url, {'disassociate': True, 'id': admin.id}, admin)
|
post(url, {'disassociate': True, 'id': admin.id}, admin)
|
||||||
assert role.members.filter(id=admin.id).count() == 0
|
assert role.members.filter(id=admin.id).count() == 0
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db
|
||||||
def test_org_admin_add_user_to_job_template(post, organization, check_jobtemplate, user):
|
def test_org_admin_add_user_to_job_template(post, organization, check_jobtemplate, user):
|
||||||
'Tests that a user with permissions to assign/revoke membership to a particular role can do so'
|
'Tests that a user with permissions to assign/revoke membership to a particular role can do so'
|
||||||
org_admin = user('org-admin')
|
org_admin = user('org-admin')
|
||||||
@@ -280,8 +281,8 @@ def test_org_admin_add_user_to_job_template(post, organization, check_jobtemplat
|
|||||||
assert joe in check_jobtemplate.execute_role
|
assert joe in check_jobtemplate.execute_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db
|
||||||
def test_org_admin_remove_user_to_job_template(post, organization, check_jobtemplate, user):
|
def test_org_admin_remove_user_from_job_template(post, organization, check_jobtemplate, user):
|
||||||
'Tests that a user with permissions to assign/revoke membership to a particular role can do so'
|
'Tests that a user with permissions to assign/revoke membership to a particular role can do so'
|
||||||
org_admin = user('org-admin')
|
org_admin = user('org-admin')
|
||||||
joe = user('joe')
|
joe = user('joe')
|
||||||
@@ -295,7 +296,7 @@ def test_org_admin_remove_user_to_job_template(post, organization, check_jobtemp
|
|||||||
assert joe not in check_jobtemplate.execute_role
|
assert joe not in check_jobtemplate.execute_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db
|
||||||
def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemplate, user):
|
def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemplate, user):
|
||||||
'Tests that a user without permissions to assign/revoke membership to a particular role cannot do so'
|
'Tests that a user without permissions to assign/revoke membership to a particular role cannot do so'
|
||||||
rando = user('rando')
|
rando = user('rando')
|
||||||
@@ -304,13 +305,14 @@ def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemp
|
|||||||
assert rando not in check_jobtemplate.admin_role
|
assert rando not in check_jobtemplate.admin_role
|
||||||
assert joe not in check_jobtemplate.execute_role
|
assert joe not in check_jobtemplate.execute_role
|
||||||
|
|
||||||
res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'id': joe.id}, rando)
|
with transaction.atomic():
|
||||||
|
res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'id': joe.id}, rando)
|
||||||
assert res.status_code == 403
|
assert res.status_code == 403
|
||||||
|
|
||||||
assert joe not in check_jobtemplate.execute_role
|
assert joe not in check_jobtemplate.execute_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db
|
||||||
def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobtemplate, user):
|
def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobtemplate, user):
|
||||||
'Tests that a user without permissions to assign/revoke membership to a particular role cannot do so'
|
'Tests that a user without permissions to assign/revoke membership to a particular role cannot do so'
|
||||||
rando = user('rando')
|
rando = user('rando')
|
||||||
@@ -320,7 +322,8 @@ def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobt
|
|||||||
assert rando not in check_jobtemplate.admin_role
|
assert rando not in check_jobtemplate.admin_role
|
||||||
assert joe in check_jobtemplate.execute_role
|
assert joe in check_jobtemplate.execute_role
|
||||||
|
|
||||||
res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'disassociate': True, 'id': joe.id}, rando)
|
with transaction.atomic():
|
||||||
|
res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'disassociate': True, 'id': joe.id}, rando)
|
||||||
assert res.status_code == 403
|
assert res.status_code == 403
|
||||||
|
|
||||||
assert joe in check_jobtemplate.execute_role
|
assert joe in check_jobtemplate.execute_role
|
||||||
|
|||||||
@@ -175,100 +175,6 @@ def test_hierarchy_rebuilding_multi_path():
|
|||||||
assert X.is_ancestor_of(D) is False
|
assert X.is_ancestor_of(D) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_hierarchy_rebuilding_loops1(organization, team):
|
|
||||||
'Tests ancestry rebuilding loops are involved'
|
|
||||||
|
|
||||||
|
|
||||||
assert team.admin_role.is_ancestor_of(organization.admin_role) is False
|
|
||||||
assert organization.admin_role.is_ancestor_of(team.admin_role)
|
|
||||||
|
|
||||||
team.admin_role.children.add(organization.admin_role)
|
|
||||||
|
|
||||||
assert team.admin_role.is_ancestor_of(organization.admin_role)
|
|
||||||
assert organization.admin_role.is_ancestor_of(team.admin_role)
|
|
||||||
|
|
||||||
team.admin_role.children.remove(organization.admin_role)
|
|
||||||
|
|
||||||
assert team.admin_role.is_ancestor_of(organization.admin_role) is False
|
|
||||||
assert organization.admin_role.is_ancestor_of(team.admin_role)
|
|
||||||
|
|
||||||
team.admin_role.children.add(organization.admin_role)
|
|
||||||
|
|
||||||
|
|
||||||
X = Role.objects.create(name='X')
|
|
||||||
X.children.add(organization.admin_role)
|
|
||||||
assert X.is_ancestor_of(team.admin_role)
|
|
||||||
assert X.is_ancestor_of(organization.admin_role)
|
|
||||||
assert organization.admin_role.is_ancestor_of(X) is False
|
|
||||||
assert team.admin_role.is_ancestor_of(X) is False
|
|
||||||
|
|
||||||
#print(X.descendents.filter(id=organization.admin_role.id).count())
|
|
||||||
#print(X.children.filter(id=organization.admin_role.id).count())
|
|
||||||
X.children.remove(organization.admin_role)
|
|
||||||
X.rebuild_role_ancestor_list()
|
|
||||||
#print(X.descendents.filter(id=organization.admin_role.id).count())
|
|
||||||
#print(X.children.filter(id=organization.admin_role.id).count())
|
|
||||||
|
|
||||||
assert X.is_ancestor_of(team.admin_role) is False
|
|
||||||
assert X.is_ancestor_of(organization.admin_role) is False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_hierarchy_rebuilding_loops():
|
|
||||||
'Tests ancestry rebuilding loops are involved'
|
|
||||||
|
|
||||||
X = Role.objects.create(name='X')
|
|
||||||
A = Role.objects.create(name='A')
|
|
||||||
B = Role.objects.create(name='B')
|
|
||||||
C = Role.objects.create(name='C')
|
|
||||||
|
|
||||||
A.children.add(B)
|
|
||||||
B.children.add(C)
|
|
||||||
C.children.add(A)
|
|
||||||
X.children.add(A)
|
|
||||||
|
|
||||||
assert X.is_ancestor_of(A)
|
|
||||||
assert X.is_ancestor_of(B)
|
|
||||||
assert X.is_ancestor_of(C)
|
|
||||||
|
|
||||||
assert A.is_ancestor_of(B)
|
|
||||||
assert A.is_ancestor_of(C)
|
|
||||||
assert B.is_ancestor_of(C)
|
|
||||||
assert B.is_ancestor_of(A)
|
|
||||||
assert C.is_ancestor_of(A)
|
|
||||||
assert C.is_ancestor_of(B)
|
|
||||||
|
|
||||||
X.children.remove(A)
|
|
||||||
X.rebuild_role_ancestor_list()
|
|
||||||
|
|
||||||
assert X.is_ancestor_of(A) is False
|
|
||||||
assert X.is_ancestor_of(B) is False
|
|
||||||
assert X.is_ancestor_of(C) is False
|
|
||||||
|
|
||||||
X.children.add(A)
|
|
||||||
|
|
||||||
assert X.is_ancestor_of(A)
|
|
||||||
assert X.is_ancestor_of(B)
|
|
||||||
assert X.is_ancestor_of(C)
|
|
||||||
|
|
||||||
C.children.remove(A)
|
|
||||||
|
|
||||||
assert A.is_ancestor_of(B)
|
|
||||||
assert A.is_ancestor_of(C)
|
|
||||||
assert B.is_ancestor_of(C)
|
|
||||||
assert B.is_ancestor_of(A) is False
|
|
||||||
assert C.is_ancestor_of(A) is False
|
|
||||||
assert C.is_ancestor_of(B) is False
|
|
||||||
|
|
||||||
assert X.is_ancestor_of(A)
|
|
||||||
assert X.is_ancestor_of(B)
|
|
||||||
assert X.is_ancestor_of(C)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_auto_parenting():
|
def test_auto_parenting():
|
||||||
org1 = Organization.objects.create(name='org1')
|
org1 = Organization.objects.create(name='org1')
|
||||||
|
|||||||
@@ -952,7 +952,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
|
|||||||
self.assertNotEqual(new_inv.groups.count(), 0)
|
self.assertNotEqual(new_inv.groups.count(), 0)
|
||||||
self.assertNotEqual(new_inv.total_hosts, 0)
|
self.assertNotEqual(new_inv.total_hosts, 0)
|
||||||
self.assertNotEqual(new_inv.total_groups, 0)
|
self.assertNotEqual(new_inv.total_groups, 0)
|
||||||
self.assertElapsedLessThan(1800) # TODO: We need to revisit this again to see if we can optimize this back to the sub-600 second range - anoek 2016-04-18
|
self.assertElapsedLessThan(600)
|
||||||
|
|
||||||
|
|
||||||
def _get_ngroups_for_nhosts(self, n):
|
def _get_ngroups_for_nhosts(self, n):
|
||||||
@@ -978,7 +978,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
|
|||||||
self.assertEqual(new_inv.groups.count(), ngroups)
|
self.assertEqual(new_inv.groups.count(), ngroups)
|
||||||
self.assertEqual(new_inv.total_hosts, nhosts)
|
self.assertEqual(new_inv.total_hosts, nhosts)
|
||||||
self.assertEqual(new_inv.total_groups, ngroups)
|
self.assertEqual(new_inv.total_groups, ngroups)
|
||||||
self.assertElapsedLessThan(180) # TODO: We need to revisit this again to see if we can optimize this back to the sub-120 second range - anoek 2016-04-18
|
self.assertElapsedLessThan(120)
|
||||||
|
|
||||||
@unittest.skipIf(getattr(settings, 'LOCAL_DEVELOPMENT', False),
|
@unittest.skipIf(getattr(settings, 'LOCAL_DEVELOPMENT', False),
|
||||||
'Skip this test in local development environments, '
|
'Skip this test in local development environments, '
|
||||||
|
|||||||
Reference in New Issue
Block a user