1. Présentation
La série d'ateliers de programmation Serverless Migration Station (tutoriels pratiques à suivre à votre rythme) et les vidéos associées sont destinées à aider les développeurs Google Cloud sans serveur à moderniser leurs applications en les guidant tout au long d'une ou plusieurs migrations, principalement en les aidant à abandonner les anciens services. Cela rend vos applications plus portables et vous offre plus d'options et de flexibilité, ce qui vous permet de vous intégrer à une plus large gamme de produits Cloud et d'y accéder, et de passer plus facilement aux versions linguistiques plus récentes. Bien qu'elle se concentre initialement sur les premiers utilisateurs de Cloud, principalement les développeurs App Engine (environnement standard), cette série est suffisamment large pour inclure d'autres plates-formes sans serveur comme Cloud Functions et Cloud Run, ou ailleurs si nécessaire.
Cet atelier de programmation vous explique comment inclure et utiliser les tâches de retrait App Engine Task Queue dans l'exemple d'application de l'atelier de programmation du module 1. Nous ajoutons son utilisation des tâches pull dans ce tutoriel du module 18, puis nous migrons cette utilisation vers Cloud Pub/Sub dans le module 19. Ceux qui utilisent les files d'attente de tâches pour les tâches push migreront plutôt vers Cloud Tasks et devront se reporter aux modules 7 à 9.
Vous apprendrez à
- Utiliser l'API/le service groupé de file d'attente des tâches App Engine
- Ajouter l'utilisation de files d'extraction à une application Python 2 Flask App Engine NDB de base
Prérequis
- Un projet Google Cloud Platform avec un compte de facturation GCP actif
- Des connaissances de base en Python
- Une connaissance correcte des commandes Linux courantes
- Des connaissances de base du développement et du déploiement d'applications App Engine
- Une application App Engine du module 1 fonctionnelle (suivez l'atelier de programmation [recommandé] ou copiez l'application depuis le dépôt).
Enquête
Comment allez-vous utiliser ce tutoriel ?
Quel est votre niveau d'expérience avec Python ?
Quel est votre niveau d'expérience avec les services Google Cloud ?
2. Arrière-plan
Pour migrer depuis les tâches de retrait App Engine Task Queue, ajoutez son utilisation à l'application Flask et App Engine NDB existante résultant de l'atelier de programmation du module 1. L'application exemple affiche les visites les plus récentes de l'utilisateur final. C'est bien, mais il est encore plus intéressant de suivre également les visiteurs pour voir qui visite le plus.
Bien que nous puissions utiliser des tâches push pour ces nombres de visiteurs, nous souhaitons répartir la responsabilité entre l'application exemple, dont le rôle est d'enregistrer les visites et de répondre immédiatement aux utilisateurs, et un "worker" désigné, dont le rôle est de comptabiliser le nombre de visiteurs en dehors du workflow de demande-réponse normal.
Pour implémenter cette conception, nous ajoutons l'utilisation de files d'extraction à l'application principale, ainsi que la prise en charge de la fonctionnalité de worker. Le nœud de calcul peut s'exécuter en tant que processus distinct (comme une instance de backend ou un code s'exécutant sur une VM toujours opérationnelle), une tâche cron ou une requête HTTP en ligne de commande de base à l'aide de curl ou wget. Après cette intégration, vous pourrez migrer l'application vers Cloud Pub/Sub dans le prochain atelier de programmation (module 19).
Ce tutoriel comprend les étapes suivantes :
- Configuration/Préparation
- Mettre à jour la configuration
- Modifier le code de l'application
3. Configuration/Préparation
Cette section explique comment effectuer les opérations suivantes :
- Configurer votre projet Cloud
- Obtenir un exemple d'application de référence
- (Re)Déployer et valider l'application de référence
Ces étapes vous permettent de commencer avec un code fonctionnel.
1. Configurer le projet
Si vous avez terminé l'atelier de programmation du module 1, réutilisez le même projet (et le même code). Vous pouvez également créer un projet ou réutiliser un autre projet existant. Assurez-vous que le projet dispose d'un compte de facturation actif et qu'une application App Engine est activée. Trouvez l'ID de votre projet, car vous en aurez besoin à plusieurs reprises dans cet atelier de programmation. Utilisez-le chaque fois que vous rencontrez la variable PROJECT_ID.
2. Obtenir un exemple d'application de référence
L'une des conditions préalables à cet atelier de programmation est de disposer d'une application App Engine fonctionnelle du module 1. Suivez l'atelier de programmation du module 1 (recommandé) ou copiez l'application du module 1 depuis le dépôt. Que vous utilisiez votre application ou la nôtre, le code de départ ("START") est celui du module 1. Cet atelier de programmation vous guide pour chaque étape jusqu'à obtenir un code similaire à celui du dossier "FINISH" du dépôt du module 18.
- START : Dossier du module 1 (Python 2)
- FINISH : Dossier du module 18 (Python 2)
- Dépôt complet (pour cloner ou télécharger le fichier ZIP)
Quelle que soit l'application du module 1 que vous utilisez, le dossier doit ressembler à la sortie ci-dessous, avec éventuellement un dossier lib :
$ ls README.md appengine_config.py requirements.txt app.yaml main.py templates
3. (Re)Déployer l'application de référence
Pour déployer l'application du module 1, procédez comme suit :
- Supprimez le dossier
libs'il existe, puis exécutezpip install -t lib -r requirements.txtpour le remplir à nouveau.libSi vous avez installé Python 2 et Python 3, vous devrez peut-être utiliser la commandepip2. - Assurez-vous d'avoir installé et initialisé l'outil de ligne de commande
gcloud, et d'avoir consulté son utilisation. - Définissez votre projet Cloud avec
gcloud config set projectPROJECT_IDsi vous ne souhaitez pas saisir votrePROJECT_IDà chaque commandegcloudémise. - Déployez l'exemple d'application avec
gcloud app deploy - Vérifiez que l'application du module 1 s'exécute comme prévu et affiche les visites les plus récentes (illustrées ci-dessous).

4. Mettre à jour la configuration
Aucune modification n'est nécessaire dans les fichiers de configuration App Engine standards (app.yaml, requirements.txt, appengine_config.py). Ajoutez plutôt un fichier de configuration queue.yaml avec le contenu suivant, en le plaçant dans le même répertoire de premier niveau :
queue:
- name: pullq
mode: pull
Le fichier queue.yaml spécifie toutes les files d'attente de tâches qui existent pour votre application (à l'exception de la file d'attente default [envoi] qui est créée automatiquement par App Engine). Dans ce cas, il n'y en a qu'une, une file d'attente pull nommée pullq. App Engine exige que l'instruction mode soit spécifiée comme pull. Sinon, il crée une file d'attente d'envoi par défaut. Pour savoir comment créer des files d'attente de retrait, consultez la documentation. Consultez également la page de référence queue.yaml pour découvrir d'autres options.
Déployez ce fichier séparément de votre application. Vous utiliserez toujours gcloud app deploy, mais vous fournirez également queue.yaml sur la ligne de commande :
$ gcloud app deploy queue.yaml Configurations to update: descriptor: [/tmp/mod18-gaepull/queue.yaml] type: [task queues] target project: [my-project] WARNING: Caution: You are updating queue configuration. This will override any changes performed using 'gcloud tasks'. More details at https://cloud.google.com/tasks/docs/queue-yaml Do you want to continue (Y/n)? Updating config [queue]...⠹WARNING: We are using the App Engine app location (us-central1) as the default location. Please use the "--location" flag if you want to use a different location. Updating config [queue]...done. Task queues have been updated. Visit the Cloud Platform Console Task Queues page to view your queues and cron jobs. $
5. Modifier le code de l'application
Cette section présente les modifications apportées aux fichiers suivants :
main.py: ajoutez l'utilisation des files d'attente de retrait à l'application principale.templates/index.html: mettez à jour le modèle Web pour afficher les nouvelles données.
Importations et constantes
La première étape consiste à ajouter une importation et plusieurs constantes pour prendre en charge les files d'extraction :
- Ajoutez une importation de la bibliothèque Task Queue,
google.appengine.api.taskqueue. - Ajoutez trois constantes pour prendre en charge la location du nombre maximal de tâches d'extraction (
TASKS) pendant une heure (HOUR) à partir de notre file d'attente d'extraction (QUEUE). - Ajoutez une constante pour afficher les visites les plus récentes ainsi que les visiteurs les plus fréquents (
LIMIT).
Vous trouverez ci-dessous le code d'origine et ce à quoi il ressemble après ces modifications :
AVANT :
from flask import Flask, render_template, request
from google.appengine.ext import ndb
app = Flask(__name__)
APRÈS :
from flask import Flask, render_template, request
from google.appengine.api import taskqueue
from google.appengine.ext import ndb
HOUR = 3600
LIMIT = 10
TASKS = 1000
QNAME = 'pullq'
QUEUE = taskqueue.Queue(QNAME)
app = Flask(__name__)
Ajouter une tâche de retrait (collecter des données pour la tâche et créer une tâche dans la file d'attente de retrait)
Le modèle de données Visit reste le même, tout comme les requêtes pour afficher les visites dans fetch_visits(). La seule modification requise dans cette partie du code se trouve dans store_visit(). En plus d'enregistrer la visite, ajoutez une tâche à la file d'attente de récupération avec l'adresse IP du visiteur afin que le nœud de calcul puisse incrémenter le compteur de visiteurs.
AVANT :
class Visit(ndb.Model):
'Visit entity registers visitor IP address & timestamp'
visitor = ndb.StringProperty()
timestamp = ndb.DateTimeProperty(auto_now_add=True)
def store_visit(remote_addr, user_agent):
'create new Visit entity in Datastore'
Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
def fetch_visits(limit):
'get most recent visits'
return Visit.query().order(-Visit.timestamp).fetch(limit)
APRÈS :
class Visit(ndb.Model):
'Visit entity registers visitor IP address & timestamp'
visitor = ndb.StringProperty()
timestamp = ndb.DateTimeProperty(auto_now_add=True)
def store_visit(remote_addr, user_agent):
'create new Visit in Datastore and queue request to bump visitor count'
Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
QUEUE.add(taskqueue.Task(payload=remote_addr, method='PULL'))
def fetch_visits(limit):
'get most recent visits'
return Visit.query().order(-Visit.timestamp).fetch(limit)
Créer un modèle de données et une fonction de requête pour le suivi des visiteurs
Ajoutez un modèle de données VisitorCount pour suivre les visiteurs. Il doit comporter des champs pour le visitor lui-même, ainsi qu'un champ counter de type entier pour suivre le nombre de visites. Ajoutez ensuite une fonction (ou une fonction Python classmethod) nommée fetch_counts() pour interroger et renvoyer les visiteurs les plus fréquents par ordre décroissant. Ajoutez la classe et la fonction juste en dessous du corps de fetch_visits() :
class VisitorCount(ndb.Model):
visitor = ndb.StringProperty(repeated=False, required=True)
counter = ndb.IntegerProperty()
def fetch_counts(limit):
'get top visitors'
return VisitCount.query().order(-VisitCount.counter).fetch(limit)
Ajouter un code de nœud de calcul
Ajoutez une fonction log_visitors() pour enregistrer les visiteurs via une requête GET à /log. Il utilise un dictionnaire/hachage pour suivre le nombre de visiteurs les plus récents, en louant autant de tâches que possible pendant une heure. Pour chaque tâche, il comptabilise toutes les visites du même visiteur. Une fois les totaux obtenus, l'application met à jour toutes les entités VisitorCount correspondantes déjà présentes dans Datastore ou en crée de nouvelles si nécessaire. La dernière étape renvoie un message en texte brut indiquant le nombre de visiteurs enregistrés sur le nombre de tâches traitées. Ajoutez cette fonction à main.py juste en dessous de fetch_counts() :
@app.route('/log')
def log_visitors():
'worker processes recent visitor counts and updates them in Datastore'
# tally recent visitor counts from queue then delete those tasks
tallies = {}
tasks = QUEUE.lease_tasks(HOUR, TASKS)
for task in tasks:
visitor = task.payload
tallies[visitor] = tallies.get(visitor, 0) + 1
if tasks:
QUEUE.delete_tasks(tasks)
# increment those counts in Datastore and return
for visitor in tallies:
counter = VisitorCount.query(VisitorCount.visitor == visitor).get()
if not counter:
counter = VisitorCount(visitor=visitor, counter=0)
counter.put()
counter.counter += tallies[visitor]
counter.put()
return 'DONE (with %d task[s] logging %d visitor[s])\r\n' % (
len(tasks), len(tallies))
Mettre à jour le gestionnaire principal avec les nouvelles données d'affichage
Pour afficher les principaux visiteurs, mettez à jour le gestionnaire principal root() afin d'appeler fetch_counts(). De plus, le modèle sera mis à jour pour afficher le nombre de visiteurs les plus actifs et les visites les plus récentes. Empaquetez le nombre de visiteurs avec les visites les plus récentes de l'appel à fetch_visits() et déposez-le dans un seul context pour le transmettre au modèle Web. Vous trouverez ci-dessous le code avant et après cette modification :
AVANT :
@app.route('/')
def root():
'main application (GET) handler'
store_visit(request.remote_addr, request.user_agent)
visits = fetch_visits(10)
return render_template('index.html', visits=visits)
APRÈS :
@app.route('/')
def root():
'main application (GET) handler'
store_visit(request.remote_addr, request.user_agent)
context = {
'limit': LIMIT,
'visits': fetch_visits(LIMIT),
'counts': fetch_counts(LIMIT),
}
return render_template('index.html', **context)
Voici toutes les modifications requises pour main.py. Vous trouverez ci-dessous une représentation visuelle de ces modifications à titre d'illustration pour vous donner une idée générale des modifications que vous apportez à main.py :

Mettre à jour le modèle Web avec de nouvelles données display
Le modèle Web templates/index.html doit être mis à jour pour afficher les principaux visiteurs en plus de la charge utile normale des visiteurs les plus récents. Déposez les visiteurs les plus fréquents et leur nombre dans un tableau en haut de la page, puis continuez à afficher les visites les plus récentes comme avant. La seule autre modification consiste à spécifier le nombre affiché à l'aide de la variable limit plutôt que de coder en dur le nombre. Voici les modifications à apporter à votre modèle Web :
AVANT :
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>
<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>
APRÈS :
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>
<h1>VisitMe example</h1>
<h3>Top {{ limit }} visitors</h3>
<table border=1 cellspacing=0 cellpadding=2>
<tr><th>Visitor</th><th>Visits</th></tr>
{% for count in counts %}
<tr><td>{{ count.visitor|e }}</td><td align="center">{{ count.counter }}</td></tr>
{% endfor %}
</table>
<h3>Last {{ limit }} visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>
Les modifications nécessaires à l'ajout de l'utilisation des tâches d'extraction App Engine Task Queue à l'exemple d'application du module 1 sont maintenant terminées. Votre répertoire représente désormais l'exemple d'application du module 18 et doit contenir les fichiers suivants :
$ ls README.md appengine_config.py queue.yaml templates app.yaml main.py requirements.txt
6. Résumé/Nettoyage
Cette section conclut cet atelier de programmation en déployant l'application et en vérifiant qu'elle fonctionne comme prévu et dans toutes les sorties reflétées. Exécutez le nœud de calcul séparément pour traiter le nombre de visiteurs. Après la validation de l'application, effectuez les étapes de nettoyage et réfléchissez aux prochaines étapes.
Déployer et vérifier l'application
Assurez-vous d'avoir déjà configuré votre file d'attente d'extraction, comme nous l'avons fait au début de cet atelier de programmation avec gcloud app deploy queue.yaml. Si vous avez terminé cette étape et que votre application exemple est prête, déployez votre application avec gcloud app deploy. Le résultat doit être identique à celui de l'application du module 1, à l'exception de la table "Top des visiteurs" qui s'affiche désormais en haut :

Bien que la nouvelle interface Web affiche les principaux visiteurs et les visites les plus récentes, sachez que le nombre de visiteurs n'inclut pas cette visite. L'application affiche le nombre de visiteurs précédents tout en déposant une nouvelle tâche qui incrémente le nombre de visiteurs dans la file d'attente, une tâche qui attend d'être traitée.
Vous pouvez exécuter la tâche en appelant /log de différentes manières :
- Un service de backend App Engine
- Un job
cron - Un navigateur Web
- Requête HTTP en ligne de commande (
curl,wget, etc.)
Par exemple, si vous utilisez curl pour envoyer une requête GET à /log, votre résultat ressemblera à ceci, à condition que vous ayez fourni votre PROJECT_ID :
$ curl https://PROJECT_ID.appspot.com/log DONE (with 1 task[s] logging 1 visitor[s])
Le nombre mis à jour sera ensuite reflété lors de la prochaine visite du site Web. Et voilà !
Bravo ! Vous avez terminé cet atelier de programmation et ajouté l'utilisation du service de file d'attente pull App Engine Task Queue à l'application exemple. Elle est maintenant prête à être migrée vers Cloud Pub/Sub, Cloud NDB et Python 3 dans le module 19.
Effectuer un nettoyage
Général
Si vous avez terminé pour le moment, nous vous recommandons de désactiver votre application App Engine pour éviter d'être facturé. Toutefois, si vous souhaitez effectuer d'autres tests ou expériences, la plate-forme App Engine dispose d'un quota sans frais. Tant que vous ne dépassez pas ce niveau d'utilisation, aucun frais ne devrait vous être facturé. Cela concerne le calcul, mais des frais peuvent également s'appliquer aux services App Engine concernés. Pour en savoir plus, consultez la page de tarification. Si cette migration implique d'autres services Cloud, ceux-ci sont facturés séparément. Dans les deux cas, le cas échéant, consultez la section "Spécifique à cet atelier de programmation" ci-dessous.
Pour être tout à fait transparent, le déploiement sur une plate-forme de calcul sans serveur Google Cloud comme App Engine entraîne de légers coûts de compilation et de stockage. Cloud Build et Cloud Storage disposent chacun de leur propre quota sans frais. Le stockage de cette image utilise une partie de ce quota. Toutefois, il est possible que vous résidiez dans une région où ce niveau sans frais n'est pas disponible. Veillez donc à surveiller votre utilisation de l'espace de stockage pour minimiser les coûts potentiels. Voici quelques "dossiers" Cloud Storage spécifiques que vous devez examiner :
console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/imagesconsole.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com- Les liens de stockage ci-dessus dépendent de votre
PROJECT_IDet de votre *LOC*ation. Par exemple, "us" si votre application est hébergée aux États-Unis.
En revanche, si vous ne souhaitez pas poursuivre avec cette application ni avec d'autres ateliers de programmation de migration associés et que vous souhaitez tout supprimer complètement, arrêtez votre projet.
Spécifique à cet atelier de programmation
Les services listés ci-dessous sont propres à cet atelier de programmation. Pour en savoir plus, consultez la documentation de chaque produit :
- Le service de file d'attente de tâches App Engine n'entraîne pas de facturation supplémentaire, comme indiqué sur la page des tarifs des anciens services groupés tels que les files d'attente de tâches.
- Le service App Engine Datastore est fourni par Cloud Datastore (Cloud Firestore en mode Datastore), qui propose également un niveau sans frais. Pour en savoir plus, consultez sa page de tarification.
Étapes suivantes
Dans cette "migration", vous avez ajouté l'utilisation de files d'attente push Task Queue à l'exemple d'application du module 1, en ajoutant la prise en charge du suivi des visiteurs, implémentant ainsi l'exemple d'application du module 18. Lors de la prochaine migration, vous allez migrer les tâches de retrait App Engine vers Cloud Pub/Sub. Depuis fin 2021, les utilisateurs ne sont plus tenus de migrer vers Cloud Pub/Sub lorsqu'ils passent à Python 3. Pour en savoir plus, consultez la section suivante.
Pour migrer vers Cloud Pub/Sub, consultez l'atelier de programmation du module 19. Au-delà de cela, d'autres migrations sont à envisager, comme Cloud Datastore, Cloud Memorystore, Cloud Storage ou Cloud Tasks (files d'attente d'envoi). Il existe également des migrations interproduits vers Cloud Run et Cloud Functions. Vous pouvez accéder à l'ensemble du contenu Serverless Migration Station (ateliers de programmation, vidéos, code source [le cas échéant]) dans son dépôt Open Source.
7. Migration vers Python 3
À l'automne 2021, l'équipe App Engine a étendu la compatibilité de nombreux services groupés aux environnements d'exécution de deuxième génération (qui disposent d'un environnement d'exécution de première génération). Par conséquent, vous n'avez plus besoin de migrer des services groupés tels que la file d'attente de tâches App Engine vers des services Cloud ou tiers autonomes tels que Cloud Pub/Sub lorsque vous portez votre application vers Python 3. En d'autres termes, vous pouvez continuer à utiliser Task Queue dans les applications Python 3 App Engine à condition de modifier le code pour accéder aux services groupés à partir des environnements d'exécution de nouvelle génération.
Pour en savoir plus sur la migration de l'utilisation des services groupés vers Python 3, consultez l'atelier de programmation du module 17 et la vidéo correspondante. Bien que ce sujet ne soit pas abordé dans le module 18, vous trouverez ci-dessous les versions Python 3 de l'application du module 1, portées vers Python 3 et utilisant toujours App Engine NDB. (Une version Python 3 de l'application du module 18 sera également disponible à un moment donné.)
8. Ressources supplémentaires
Vous trouverez ci-dessous des ressources supplémentaires pour les développeurs qui souhaitent en savoir plus sur ce module de migration ou sur des modules et produits associés. Vous y trouverez des liens vers le code, des informations sur la façon de nous faire part de vos commentaires sur ce contenu et divers éléments de documentation qui pourraient vous être utiles.
Problèmes/commentaires concernant l'atelier de programmation
Si vous rencontrez des problèmes avec cet atelier de programmation, commencez par faire une recherche avant de les signaler. Liens vers la recherche et la création d'un signalement :
Ressources de migration
Vous trouverez des liens vers les dossiers des dépôts du module 1 (code de départ "START") et du module 18 (code final "FINISH") dans le tableau ci-dessous. Vous pouvez également y accéder depuis le dépôt pour toutes les migrations d'ateliers de programmation App Engine. Clonez-le ou téléchargez-le sous forme de fichier ZIP.
Atelier de programmation | Python 2 | Python 3 |
code (non présenté dans ce tutoriel) | ||
Module 18 (cet atelier de programmation) | N/A |
Références en ligne
Vous trouverez ci-dessous des ressources utiles pour ce tutoriel :
File d'attente de tâches d'App Engine
- Présentation d'App Engine Task Queue
- Présentation des files d'attente de tâches App Engine
- Exemple d'application complète de file d'attente pull App Engine Task Queue
- Créer des files d'attente de retrait Task Queue
- Vidéo de lancement de la file d'attente Google I/O 2011 ( exemple d'application Votelator)
- Documentation de référence sur
queue.yaml queue.yamlvs Cloud Tasks- Guide de migration des files d'attente de retrait vers Pub/Sub
- Exemple de documentation sur la migration des files d'attente de retrait App Engine Task Queue vers Cloud Pub/Sub
Plate-forme App Engine
Documentation App Engine
Environnement d'exécution Python 2 App Engine (environnement standard)
Environnement d'exécution Python 3 App Engine (environnement standard)
Différences entre les environnements d'exécution Python 2 et Python 3 App Engine (environnement standard)
Guide de migration d'App Engine (environnement standard) de Python 2 vers Python 3
Informations sur les tarifs et les quotas d'App Engine
Lancement de la plate-forme App Engine de deuxième génération (2018)
Compatibilité à long terme avec les anciens environnements d'exécution
Exemples de migration de la documentation
Informations sur les autres clouds
- Python sur Google Cloud Platform
- Bibliothèques clientes Google Cloud Python
- Niveau "Toujours sans frais" de Google Cloud
- SDK Google Cloud (outil de ligne de commande
gcloud) - Toute la documentation Google Cloud
Vidéos
- Serverless Migration Station
- Expéditions sans serveur
- S'abonner à Google Cloud Tech
- S'abonner à Google Developers
Licence
Ce document est publié sous une licence Creative Commons Attribution 2.0 Generic.