Development #32248
tenant creation failed (Cannot query "utilisateur": Must be "ContentType" instance.)
0%
Description
Avec le dernier commit de django-tenant-schemas 631743c45eb428a351c0717183efbff897776f47, échec du build: https://jenkins2.entrouvert.org/job/hobo/415/console
et chez moi:
(publik-env) chris@devinst:~/src/hobo$ DJANGO_SETTINGS_MODULE=authentic2.settings DEBIAN_CONFIG_COMMON=debian/debian_config_common.py AUTHENTIC2_SETTINGS_FILE=tests_authentic/settings.py py.test -s --disable-pytest-warnings tests_authentic/test_hobo_deploy.py -x =========================================================================================== FAILURES =========================================================================================== _______________________________________________________________________________________ test_hobo_deploy _______________________________________________________________________________________ tenant_base = '/tmp/tmpPt4oPHauthentic-tenant-base', mocker = <pytest_mock.MockFixture object at 0x7f01439fe610>, skeleton_dir = '/tmp/tmpiM0Pqiskeletons' def test_hobo_deploy(tenant_base, mocker, skeleton_dir): from django.core.management import call_command from django.conf import settings # Create skeleton roles.json os.makedirs(os.path.join(skeleton_dir, 'commune', 'wcs')) with open(os.path.join(skeleton_dir, 'commune', 'wcs', 'roles.json'), 'w') as roles_json: json.dump([ { 'model': 'a2_rbac.role', 'fields': { 'name': u'Service petite enfance', 'slug': u'service-petite-enfance', }, }, { 'model': 'a2_rbac.role', 'fields': { 'name': u'Service état-civil', 'slug': u'service-etat-civil', }, }, ], roles_json) # As a user is created, notify_agents is called, as celery is not running # we just block it mocker.patch('hobo.agent.authentic2.provisionning.notify_agents') requests_get = mocker.patch('requests.get') meta1 = '''<?xml version="1.0"?> <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="http://eservices.example.net/saml/metadata"> <SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <KeyDescriptor use="signing"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"> <RSAKeyValue> <Modulus>nJpkBznHNbvE+RAC6mU+NPQnIWs8gFNCm6I3FPcUKYpaJbXaurJ4cJgvnaEiqIXPQDcbHxuLeCbYbId9yascWZirvQbh8d/r+Vv+24bPG++9gW+i3Nnz1VW8V+z0b+puHWvM/FjJjBNJgWkI38gaupz47U6/02CtWx00stitiwk=</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> </KeyValue> </ds:KeyInfo> </KeyDescriptor> <KeyDescriptor use="encryption"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"> <RSAKeyValue> <Modulus>3BxSiAzGvY1Yuqa31L7Zr2WHM/8cn5oX+Q6A2SYgzjuvAgnWyizN8YgW/fHR4G7MtkmZ5RFJLXfcSLwbUfpFHV6KO1ikbgViYuFempM+SWtjqEI7ribm9GaI5kUzHJZBrH3/Q9XAd9/GLLALxurGjbKDeLfc0D+7el26g4sYmA8=</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> </KeyValue> </ds:KeyInfo> </KeyDescriptor> <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://eservices.example.net/saml/singleLogout" ResponseLocation="http://eservices.example.net/saml/singleLogoutReturn" /> <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://eservices.example.net/saml/singleLogoutSOAP" /> <ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://eservices.example.net/saml/manageNameId" ResponseLocation="http://eservices.example.net/saml/manageNameIdReturn" /> <ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://eservices.example.net/saml/manageNameIdSOAP" /> <AssertionConsumerService isDefault="true" index="0" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="http://eservices.example.net/saml/assertionConsumerArtifact" /> <AssertionConsumerService index="1" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://eservices.example.net/saml/assertionConsumerPost" /> <AssertionConsumerService index="2" Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="http://eservices.example.net/saml/assertionConsumerSOAP" /> <AssertionConsumerService index="3" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://eservices.example.net/saml/assertionConsumerRedirect" /> </SPSSODescriptor> </EntityDescriptor>''' meta2 = meta1.replace('eservices', 'passerelle') meta3 = meta1.replace('eservices', 'clapiers') metadatas = [meta1, meta2, meta3] side_effect = [] for meta in metadatas: m = mock.Mock() m.text = meta side_effect.append(m) requests_get.side_effect = side_effect env = { 'users': [ { 'username': 'john.doe', 'first_name': 'John', 'last_name': 'Doe', 'email': 'john.doe@example.net', 'password': 'password', }, ], 'profile': { 'fields': [ { 'kind': 'title', 'description': '', 'required': False, 'user_visible': True, 'label': u'Civilité', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'title' }, { 'kind': 'string', 'description': '', 'required': True, 'user_visible': True, 'label': u'Prénom', 'disabled': False, 'user_editable': True, 'asked_on_registration': True, 'name': 'first_name' }, { 'kind': 'string', 'description': '', 'required': True, 'user_visible': True, 'label': 'Nom', 'disabled': False, 'user_editable': True, 'asked_on_registration': True, 'name': 'last_name' }, { 'kind': 'email', 'description': '', 'required': True, 'user_visible': True, 'label': u'Adresse électronique', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'email' }, { 'kind': 'string', 'description': '', 'required': False, 'user_visible': True, 'label': 'Addresse', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'address' }, { 'kind': 'string', 'description': '', 'required': False, 'user_visible': True, 'label': 'Code postal', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'zipcode' }, { 'kind': 'string', 'description': '', 'required': False, 'user_visible': True, 'label': 'Commune', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'city' }, { 'kind': 'string', 'description': '', 'required': False, 'user_visible': True, 'label': u'Téléphone', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'phone' }, { 'kind': 'string', 'description': '', 'required': False, 'user_visible': True, 'label': 'Mobile', 'disabled': False, 'user_editable': True, 'asked_on_registration': False, 'name': 'mobile' }, { 'kind': 'string', 'description': '', 'required': False, 'user_visible': True, 'label': 'Pays', 'disabled': True, 'user_editable': True, 'asked_on_registration': False, 'name': 'country' }, { 'kind': 'birthdate', 'description': '', 'required': False, 'user_visible': True, 'label': 'Date de naissance', 'disabled': True, 'user_editable': True, 'asked_on_registration': False, 'name': 'birthdate' } ] }, 'variables': { 'hobo_test_variable': True, 'other_variable': 'foo', }, 'services': [ { 'service-id': 'authentic', 'slug': 'test', 'title': 'Test', 'this': True, 'secret_key': '12345', 'base_url': 'http://sso.example.net', 'variables': { 'other_variable': 'bar', } }, { 'service-id': 'wcs', 'template_name': 'commune', 'slug': 'montpellier-metropole', 'title': u'Montpellier-Métropole', 'base_url': 'http://eservices.example.net', 'saml-sp-metadata-url': 'http://eservices.example.net/saml/metadata', }, { 'service-id': 'passerelle', 'slug': 'passerelle', 'title': u'Passerelle', 'base_url': 'http://passerelle.example.net', 'saml-sp-metadata-url': 'http://passerelle.example.net/saml/metadata', }, { 'service-id': 'wcs', 'template_name': 'commune', 'slug': 'clapiers', 'title': 'Clapiers', 'base_url': 'http://clapiers.example.net', 'saml-sp-metadata-url': 'http://clapiers.example.net/saml/metadata', }, ] } hobo_json_content = json.dumps(env) hobo_json = tempfile.NamedTemporaryFile() hobo_json.write(hobo_json_content) hobo_json.flush() > call_command('hobo_deploy', 'http://sso.example.net', hobo_json.name) tests_authentic/test_hobo_deploy.py:309: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../envs/publik-env/local/lib/python2.7/site-packages/django/core/management/__init__.py:131: in call_command return command.execute(*args, **defaults) hobo/agent/authentic2/apps.py:45: in new_execute return old_execute(self, *args, **kwargs) ../../envs/publik-env/local/lib/python2.7/site-packages/django/core/management/base.py:330: in execute output = self.handle(*args, **options) hobo/agent/common/management/commands/hobo_deploy.py:65: in handle self.deploy(base_url, hobo_environment, ignore_timestamp) hobo/agent/common/management/commands/hobo_deploy.py:78: in deploy call_command('create_tenant', domain) ../../envs/publik-env/local/lib/python2.7/site-packages/django/core/management/__init__.py:131: in call_command return command.execute(*args, **defaults) hobo/agent/authentic2/apps.py:45: in new_execute return old_execute(self, *args, **kwargs) ../../envs/publik-env/local/lib/python2.7/site-packages/django/core/management/base.py:330: in execute output = self.handle(*args, **options) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <hobo.multitenant.management.commands.create_tenant.Command object at 0x7f0143615050>, hostnames = ['sso.example.net'] options = {'no_color': False, 'pythonpath': None, 'settings': None, 'skip_checks': True, ...}, verbosity = 1, hostname = 'sso.example.net', tenant_base = '/tmp/tmpPt4oPHauthentic-tenant-base' tenant_dir = '/tmp/tmpPt4oPHauthentic-tenant-base/sso.example.net', tenant_dir_tmp = '/tmp/tmpPt4oPHauthentic-tenant-base/sso.example.net__CREATION_IN_PROGRESS.invalid' e = ValueError('Cannot query "utilisateur": Must be "ContentType" instance.',) def handle(self, hostnames, **options): verbosity = int(options.get('verbosity')) if not hostnames: raise CommandError("you must give at least one tenant hostname") if '-' in hostnames: # get additional list of hostnames from stdin hostnames = list(hostnames) hostnames.remove('-') hostnames.extend([x.strip() for x in sys.stdin.readlines()]) for hostname in hostnames: try: tenant_base = TenantMiddleware.base() except AttributeError: raise CommandError("you must configure TENANT_BASE in your settings") if not tenant_base: raise CommandError("you must set a value to TENANT_BASE in your settings") tenant_dir = os.path.join(tenant_base, hostname) if os.path.exists(tenant_dir): raise CommandError('tenant already exists') tenant_dir_tmp = os.path.join( tenant_base, hostname + '__CREATION_IN_PROGRESS.invalid') try: os.mkdir(tenant_dir_tmp, 0755) except OSError as e: raise CommandError('cannot start tenant creation (%s)' % str(e)) # tenant creation in database try: connection.set_schema_to_public() schema = TenantMiddleware.hostname2schema(hostname) tenant = get_tenant_model()(schema_name=schema, domain_url=hostname) if verbosity >= 1: print print self.style.NOTICE("=== Creating schema ") \ + self.style.SQL_TABLE(tenant.schema_name) with transaction.atomic(): tenant.create_schema(check_if_exists=True) except Exception as e: os.rmdir(tenant_dir_tmp) > raise CommandError('tenant creation failed (%s)' % str(e)) E CommandError: tenant creation failed (Cannot query "utilisateur": Must be "ContentType" instance.) hobo/multitenant/management/commands/create_tenant.py:54: CommandError
Fichiers
Historique
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
Bug introduit dans #26206, le cache doit être par schema, par ModelManager mais donc aussi par Model puisque durant les migrations des classes de modèles sont créées qui diffèrent (et apparemment partagent le même ModelManager ou bien un truc m'échappe).
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
- Fichier 0001-postgresql_backend-cache-ContentType-based-on-the-ma.patch 0001-postgresql_backend-cache-ContentType-based-on-the-ma.patch ajouté
- Tracker changé de Bug à Development
- Statut changé de Nouveau à Solution proposée
- Patch proposed changé de Non à Oui
En fait je m'étais planté entre obj et owner dans mon descripteur, mais au
passage je double le niveau de cache pour aussi prendre en compte la classe du
modèle, au cas où, dans le cas général on aura un seul manager et une seule
classe de modèle, juste le tenant et le thread qui varient.
Mis à jour par Christophe Siraut il y a environ 5 ans
Pas fan de l'idée doubler le niveau de cache si ce n'est pas nécessaire; mais si c'est ce qu'on veut est-ce qu'il ne faudrait pas plutôt :
model_cache = global_cache.setdefault(obj.model, weakref.WeakKeyDictionary()) get_cache = model_cache.get(obj) if not get_cache: […] global_cache[obj] = get_cache
Plus loin la fonction retourne get_cache(schema_name), mais schema_name étant un CharField la fonction échouerait sur l'attribut model. Est-ce qu'il ne manque pas une étape, si j'ai bien compris on veut un cache par tenant, par modèle, et par objet? (Hmm, je te relis " par schema, par ModelManager mais donc aussi par Model", bref je n'ai pas compris, je vais reprendre plus tard)
durant les migrations des classes de modèles sont créées qui diffèrent
Peux-tu pointer comment un modèle est initié avec des classes différentes?
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
Christophe Siraut a écrit :
Pas fan de l'idée doubler le niveau de cache si ce n'est pas nécessaire; mais si c'est ce qu'on veut est-ce qu'il ne faudrait pas plutôt :
[...]
Je ne vois aucune de différence entre obj.model/obj ou obj/obj.model, c'est plus clair pour moi de partir du modèle puis de vérifier le Manager.
Plus loin la fonction retourne get_cache(schema_name), mais schema_name étant un CharField la fonction échouerait sur l'attribut model. Est-ce qu'il ne manque
schema_name est une chaîne, je ne sais pas pourquoi tu parles de CharField, les tenants ne sont pas stockés en base.
Peux-tu pointer comment un modèle est initié avec des classes différentes?
Par l'exécuteur de migration, tu peux lire le code dans django.db.migrations si tu veux.
Mis à jour par Christophe Siraut il y a environ 5 ans
- Statut changé de Solution proposée à Solution validée
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
- Statut changé de Solution validée à Résolu (à déployer)
commit 22d7ace8c0b8eb379c8cf620716317145906371a Author: Benjamin Dauvergne <bdauvergne@entrouvert.com> Date: Fri Apr 12 17:13:46 2019 +0200 postgresql_backend: cache ContentType based on the manager object and model classes (#32248) model classes and manager object are duplicated during migrations.
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
- Fichier 0001-postgresql_backend-clear-_cache-on-current-ContentTy.patch 0001-postgresql_backend-clear-_cache-on-current-ContentTy.patch ajouté
- Statut changé de Résolu (à déployer) à Solution proposée
My bad, j'ai viré le del ContentType.objects._cache
qui était encore nécessaire.
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
- Fichier 0001-tenant_schemas-augment-tests-on-cache-of-contenttype.patch 0001-tenant_schemas-augment-tests-on-cache-of-contenttype.patch ajouté
- Fichier 0002-postgresql_backend-fix-typo-in-contenttypes-cache-ac.patch 0002-postgresql_backend-fix-typo-in-contenttypes-cache-ac.patch ajouté
Les tests ne testaient pas car la table contenttype était partagé, maintenant ça va mieux.
Il restait une typo sur une récupération de get_cache dans global_cache au lieu de model_cache et je ne traitais pas ContentType.objects._cache qui des fois n'est pas dans local_managers.
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
Et Christophe m'avait signalé le souci plus haut mais je n'avais pas compris :/
Mis à jour par Christophe Siraut il y a environ 5 ans
- Statut changé de Solution proposée à Solution validée
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
- Statut changé de Solution validée à Résolu (à déployer)
commit c5d54b917e4cec65c0c8311fea235b2e537ec31e Author: Benjamin Dauvergne <bdauvergne@entrouvert.com> Date: Mon Apr 15 13:48:06 2019 +0200 postgresql_backend: fix typo in contenttypes cache access and clear base Content.objects._cache (#32248) commit 68c98a6267b8a702e4f4e5c2e4351905835520ef Author: Benjamin Dauvergne <bdauvergne@entrouvert.com> Date: Mon Apr 15 13:47:35 2019 +0200 tenant_schemas: augment tests on cache of contenttypes (#32248)
Mis à jour par Benjamin Dauvergne il y a environ 5 ans
- Statut changé de Résolu (à déployer) à Solution déployée