Projet

Général

Profil

Development #32248

tenant creation failed (Cannot query "utilisateur": Must be "ContentType" instance.)

Ajouté par Christophe Siraut il y a environ 5 ans. Mis à jour il y a environ 5 ans.

Statut:
Fermé
Priorité:
Normal
Assigné à:
Catégorie:
-
Version cible:
-
Début:
12 avril 2019
Echéance:
% réalisé:

0%

Temps estimé:
Patch proposed:
Oui
Planning:

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

#1

Mis à jour par Frédéric Péters il y a environ 5 ans

  • Privée changé de Oui à Non
#3

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).

#4

Mis à jour par Benjamin Dauvergne il y a environ 5 ans

  • Assigné à mis à Benjamin Dauvergne
#5

Mis à jour par Benjamin Dauvergne il y a environ 5 ans

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.

#6

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?

#7

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.

#8

Mis à jour par Christophe Siraut il y a environ 5 ans

  • Statut changé de Solution proposée à Solution validée
#9

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.
#10

Mis à jour par Benjamin Dauvergne il y a environ 5 ans

My bad, j'ai viré le del ContentType.objects._cache qui était encore nécessaire.

#11

Mis à jour par Benjamin Dauvergne il y a environ 5 ans

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.

#12

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 :/

#13

Mis à jour par Christophe Siraut il y a environ 5 ans

  • Statut changé de Solution proposée à Solution validée
#14

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)
#15

Mis à jour par Benjamin Dauvergne il y a environ 5 ans

  • Statut changé de Résolu (à déployer) à Solution déployée

Formats disponibles : Atom PDF