1. Introduction
Les workflows sont un cas d'utilisation courant dans l'analyse de données ; ils impliquent l'ingestion, la transformation et l'analyse de données pour déduire les informations significatives. Dans Google Cloud Platform, l'orchestration des workflows s'effectue dans Cloud Composer, version hébergée de l'outil de workflow Open Source communément utilisé Apache Airflow. Dans cet atelier, vous allez utiliser Cloud Composer pour créer un workflow simple qui crée un cluster Cloud Dataproc, l'analyse à l'aide de Cloud Dataproc et d'Apache Hadoop, puis supprime le cluster Cloud Dataproc.
Qu'est-ce que Cloud Composer ?
Cloud Composer est un service d'orchestration de workflows entièrement géré qui vous permet de créer, de programmer et de surveiller vos pipelines dans des clouds ou des centres de données sur site. Basé sur le projet Open Source populaire Apache Airflow et sur le langage de programmation Python, Cloud Composer est facile à utiliser et permet le libre choix du fournisseur.
En utilisant Cloud Composer au lieu d'une instance locale d'Apache Airflow, les utilisateurs peuvent profiter du meilleur d'Airflow sans coûts d'installation ni de gestion.
Qu'est-ce qu'Apache Airflow ?
Apache Airflow est un outil Open Source utilisé pour créer, planifier et surveiller des workflows de manière programmatique. Voici quelques termes clés à retenir concernant Airflow, que vous rencontrerez tout au long de l'atelier :
- DAG : un DAG (Directed Acyclic Graph) est un ensemble de tâches organisées que vous souhaitez programmer et exécuter. Les DAG, également appelés workflows, sont définis dans des fichiers Python standards.
- Opérateur : un opérateur décrit une seule tâche dans un workflow.
Qu'est-ce que Cloud Dataproc ?
Cloud Dataproc est le service Apache Spark et Apache Hadoop entièrement géré de Google Cloud Platform. Cloud Dataproc s'intègre facilement à d'autres services GCP. Vous bénéficiez ainsi d'une plate-forme performante et complète pour vos tâches de traitement des données, d'analyse et de machine learning.
Objectifs de l'atelier
Cet atelier de programmation vous explique comment créer et exécuter un workflow Apache Airflow dans Cloud Composer qui effectue les tâches suivantes :
- Créer un cluster Cloud Dataproc
- Exécute une tâche Apache Hadoop de décompte de mots sur le cluster et transfère ses résultats vers Cloud Storage
- Suppression du cluster
Points abordés
- Créer et exécuter un workflow Apache Airflow dans Cloud Composer
- Utiliser Cloud Composer et Cloud Dataproc pour exécuter une analyse sur un ensemble de données
- Accéder à votre environnement Cloud Composer par l'intermédiaire de la console Google Cloud Platform, du SDK Cloud et de l'interface Web Airflow
Prérequis
- Compte GCP
- Connaissances de base de la CLI
- Connaissances de base sur Python
2. Configurer GCP
Créer le projet
Sélectionnez ou créez un projet Google Cloud Platform.
Notez l'ID de votre projet, car vous en aurez besoin lors des prochaines étapes.
Si vous créez un projet, l'ID du projet se trouve juste en dessous du nom du projet sur la page de création. |
|
Si vous avez déjà créé un projet, vous trouverez son ID sur la page d'accueil de la console, dans la fiche "Informations sur le projet". |
|
Activer les API
Activez les API Cloud Composer, Cloud Dataproc et Cloud Storage. Une fois activées, vous pouvez ignorer le bouton "Accéder aux identifiants" et passer à l'étape suivante du tutoriel. |
|
Créer un environnement Composer
Créez un environnement Cloud Composer avec la configuration suivante :
Vous pouvez conserver les autres configurations par défaut. Cliquez sur "Créer" en bas de l'écran. |
|
Créer un bucket Cloud Storage
Dans votre projet, créez un bucket Cloud Storage avec la configuration suivante :
Lorsque vous êtes prêt, appuyez sur "Créer". |
|
3. Configurer Apache Airflow
Afficher les informations sur l'environnement Composer
Dans la console GCP, ouvrez la page Environnements.
Cliquez sur le nom de l'environnement pour en afficher les détails.
La page Détails de l'environnement contient des informations telles que l'URL de l'interface Web d'Airflow, l'ID du cluster Google Kubernetes Engine, le nom du bucket Cloud Storage et le chemin d'accès au dossier /dags.
Dans Airflow, un DAG (Directed Acyclic Graph) est un ensemble de tâches organisées que vous souhaitez planifier et exécuter. Les DAG, également appelés workflows, sont définis dans des fichiers Python standards. Cloud Composer ne programme que les DAG figurant dans le dossier /dags. Le dossier /dags se trouve dans le bucket Cloud Storage que Cloud Composer crée automatiquement lorsque vous créez votre environnement.
Définir des variables d'environnement Apache Airflow
Les variables Apache Airflow sont un concept spécifique à Airflow, et se distinguent des variables d'environnement. Dans cette étape, vous allez définir les trois variables Airflow suivantes : gcp_project, gcs_bucket et gce_zone.
Utiliser gcloud pour définir des variables
Commencez par ouvrir Cloud Shell, sur lequel le SDK Cloud est déjà installé.
Définissez la variable d'environnement COMPOSER_INSTANCE sur le nom de votre environnement Composer.
COMPOSER_INSTANCE=my-composer-environment
Pour définir des variables Airflow à l'aide de l'outil de ligne de commande gcloud, utilisez la commande gcloud composer environments run avec la sous-commande variables. Cette commande gcloud composer exécute la sous-commande CLI Airflow variables. La sous-commande transmet les arguments à l'outil de ligne de commande gcloud.
Vous exécuterez cette commande trois fois, en remplaçant les variables par celles qui concernent votre projet.
Définissez gcp_project à l'aide de la commande suivante, en remplaçant <your-project-id> par l'ID du projet que vous avez noté à l'étape 2.
gcloud composer environments run ${COMPOSER_INSTANCE} \
--location us-central1 variables -- --set gcp_project <your-project-id>
Le résultat doit se présenter comme suit :
kubeconfig entry generated for us-central1-my-composer-env-123abc-gke.
Executing within the following Kubernetes cluster namespace: composer-1-10-0-airflow-1-10-2-123abc
[2020-04-17 20:42:49,713] {settings.py:176} INFO - settings.configure_orm(): Using pool settings. pool_size=5, pool_recycle=1800, pid=449
[2020-04-17 20:42:50,123] {default_celery.py:90} WARNING - You have configured a result_backend of redis://airflow-redis-service.default.svc.cluste
r.local:6379/0, it is highly recommended to use an alternative result_backend (i.e. a database).
[2020-04-17 20:42:50,127] {__init__.py:51} INFO - Using executor CeleryExecutor
[2020-04-17 20:42:50,433] {app.py:52} WARNING - Using default Composer Environment Variables. Overrides have not been applied.
[2020-04-17 20:42:50,440] {configuration.py:522} INFO - Reading the config from /etc/airflow/airflow.cfg
[2020-04-17 20:42:50,452] {configuration.py:522} INFO - Reading the config from /etc/airflow/airflow.cfg
Définissez gcs_bucket à l'aide de la commande suivante, en remplaçant <your-bucket-name> par l'ID du bucket que vous avez noté à l'étape 2. Si vous avez suivi nos recommandations, le nom de votre bucket est identique à l'ID de votre projet. Votre résultat sera semblable à celui de la commande précédente.
gcloud composer environments run ${COMPOSER_INSTANCE} \
--location us-central1 variables -- --set gcs_bucket gs://<your-bucket-name>
Définissez gce_zone à l'aide de la commande suivante. Votre résultat sera semblable à celui des commandes précédentes.
gcloud composer environments run ${COMPOSER_INSTANCE} \
--location us-central1 variables -- --set gce_zone us-central1-a
(Facultatif) Utiliser gcloud pour afficher une variable
Pour afficher la valeur d'une variable, exécutez la sous-commande CLI Airflow variables avec l'argument get ou utilisez l'interface utilisateur Airflow.
Exemple :
gcloud composer environments run ${COMPOSER_INSTANCE} \
--location us-central1 variables -- --get gcs_bucket
Vous pouvez le faire avec l'une des trois variables que vous venez de définir : gcp_project, gcs_bucket et gce_zone.
4. Exemple de workflow
Examinons le code du DAG que nous utiliserons à l'étape 5. Ne vous inquiétez pas pour le moment de télécharger des fichiers, suivez simplement les instructions.
Il y a beaucoup d'éléments à aborder ici, alors examinons-les plus en détail.
from airflow import models
from airflow.contrib.operators import dataproc_operator
from airflow.utils import trigger_rule
Commençons par importer des éléments Airflow :
airflow.models: nous permet d'accéder aux données et d'en créer dans la base de données Airflow.airflow.contrib.operators: où se trouvent les opérateurs de la communauté. Dans ce cas, nous avons besoin dedataproc_operatorpour accéder à l'API Cloud Dataproc.airflow.utils.trigger_rule: pour ajouter des règles de déclenchement à nos opérateurs. Les règles de déclenchement permettent de contrôler précisément si un opérateur doit s'exécuter en fonction de l'état de ses parents.
output_file = os.path.join(
models.Variable.get('gcs_bucket'), 'wordcount',
datetime.datetime.now().strftime('%Y%m%d-%H%M%S')) + os.sep
Cela spécifie l'emplacement de notre fichier de sortie. La ligne à retenir ici est models.Variable.get('gcs_bucket'), qui récupère la valeur de la variable gcs_bucket à partir de la base de données Airflow.
WORDCOUNT_JAR = (
'file:///usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar'
)
input_file = 'gs://pub/shakespeare/rose.txt'
wordcount_args = ['wordcount', input_file, output_file]
WORDCOUNT_JAR: emplacement du fichier .jar que nous exécuterons sur le cluster Cloud Dataproc. Il est déjà hébergé sur GCP pour vous.input_file: emplacement du fichier contenant les données sur lesquelles notre job Hadoop effectuera les calculs. Nous importerons les données à cet emplacement à l'étape 5.wordcount_args: arguments que nous transmettrons au fichier JAR.
yesterday = datetime.datetime.combine(
datetime.datetime.today() - datetime.timedelta(1),
datetime.datetime.min.time())
Nous obtenons ainsi un objet datetime équivalent représentant minuit la veille. Par exemple, si cette fonction est exécutée à 11h00 le 4 mars, l'objet datetime représente 00h00 le 3 mars. Cela s'explique par la façon dont Airflow gère la planification. Pour en savoir plus, cliquez ici.
default_dag_args = {
'start_date': yesterday,
'email_on_failure': False,
'email_on_retry': False,
'retries': 1,
'retry_delay': datetime.timedelta(minutes=5),
'project_id': models.Variable.get('gcp_project')
}
La variable default_dag_args sous la forme d'un dictionnaire doit être fournie chaque fois qu'un DAG est créé :
'email_on_failure': indique si des alertes par e-mail doivent être envoyées en cas d'échec d'une tâche.'email_on_retry': indique si des alertes par e-mail doivent être envoyées lorsqu'une tâche est relancée.'retries': indique le nombre de nouvelles tentatives qu'Airflow doit effectuer en cas d'échec d'un DAG.'retry_delay': indique le temps d'attente d'Airflow avant de réessayer.'project_id': indique au DAG l'ID du projet GCP auquel l'associer, qui sera nécessaire ultérieurement avec l'opérateur Dataproc.
with models.DAG(
'composer_hadoop_tutorial',
schedule_interval=datetime.timedelta(days=1),
default_args=default_dag_args) as dag:
L'utilisation de with models.DAG indique au script d'inclure tout ce qui se trouve en dessous dans le même DAG. Nous voyons également trois arguments transmis :
- La première, une chaîne, est le nom à donner au DAG que nous créons. Dans ce cas, nous utilisons
composer_hadoop_tutorial. schedule_interval: un objetdatetime.timedelta, que nous avons défini ici sur un jour. Cela signifie que ce DAG tentera de s'exécuter une fois par jour après le'start_date'défini précédemment dans'default_dag_args'.default_args: dictionnaire que nous avons créé précédemment et qui contient les arguments par défaut du DAG
Créer un cluster Dataproc
Ensuite, nous allons créer un dataproc_operator.DataprocClusterCreateOperator qui crée un cluster Cloud Dataproc.
create_dataproc_cluster = dataproc_operator.DataprocClusterCreateOperator(
task_id='create_dataproc_cluster',
cluster_name='composer-hadoop-tutorial-cluster-{{ ds_nodash }}',
num_workers=2,
zone=models.Variable.get('gce_zone'),
master_machine_type='n1-standard-1',
worker_machine_type='n1-standard-1')
Dans cet opérateur, nous voyons quelques arguments, dont tous sauf le premier sont spécifiques à cet opérateur :
task_id: comme dans BashOperator, il s'agit du nom que nous attribuons à l'opérateur, qui est visible dans l'interface utilisateur Airflow.cluster_name: nom que nous attribuons au cluster Cloud Dataproc. Ici, nous l'avons nommécomposer-hadoop-tutorial-cluster-{{ ds_nodash }}(voir l'encadré d'informations pour les informations supplémentaires facultatives).num_workers: nombre de nœuds de calcul que nous attribuons au cluster Cloud Dataproczone: région géographique dans laquelle nous souhaitons que le cluster soit situé, telle qu'elle est enregistrée dans la base de données Airflow. Cela permettra de lire la variable'gce_zone'que nous avons définie à l'étape 3.master_machine_type: type de machine que nous souhaitons attribuer au maître Cloud Dataprocworker_machine_type: type de machine que nous souhaitons attribuer au nœud de calcul Cloud Dataproc
Envoyer une tâche Apache Hadoop
dataproc_operator.DataProcHadoopOperator nous permet d'envoyer une tâche à un cluster Cloud Dataproc.
run_dataproc_hadoop = dataproc_operator.DataProcHadoopOperator(
task_id='run_dataproc_hadoop',
main_jar=WORDCOUNT_JAR,
cluster_name='composer-hadoop-tutorial-cluster-{{ ds_nodash }}',
arguments=wordcount_args)
Nous fournissons plusieurs paramètres :
task_id: nom que nous attribuons à cette partie du DAG.main_jar: emplacement du fichier .jar que nous souhaitons exécuter sur le clustercluster_name: nom du cluster sur lequel exécuter le job. Vous remarquerez qu'il est identique à celui de l'opérateur précédent.arguments: arguments transmis au fichier .jar, comme si vous exécutiez le fichier .jar à partir de la ligne de commande
Supprimer le cluster
Le dernier opérateur que nous allons créer est dataproc_operator.DataprocClusterDeleteOperator.
delete_dataproc_cluster = dataproc_operator.DataprocClusterDeleteOperator(
task_id='delete_dataproc_cluster',
cluster_name='composer-hadoop-tutorial-cluster-{{ ds_nodash }}',
trigger_rule=trigger_rule.TriggerRule.ALL_DONE)
Comme son nom l'indique, cet opérateur supprime un cluster Cloud Dataproc donné. Nous voyons ici trois arguments :
task_id: comme dans BashOperator, il s'agit du nom que nous attribuons à l'opérateur, qui est visible dans l'interface utilisateur Airflow.cluster_name: nom que nous attribuons au cluster Cloud Dataproc. Ici, nous l'avons nommécomposer-hadoop-tutorial-cluster-{{ ds_nodash }}(pour en savoir plus, consultez l'encadré d'informations après "Créer un cluster Dataproc").trigger_rule: nous avons brièvement mentionné les règles de déclenchement lors des importations au début de cette étape, mais voici un exemple concret. Par défaut, un opérateur Airflow ne s'exécute que si tous ses opérateurs en amont ont abouti. La règle de déclenchementALL_DONEexige uniquement que tous les opérateurs en amont aient été exécutés, qu'ils aient réussi ou non. Dans ce cas, cela signifie que même si le job Hadoop a échoué, nous voulons toujours arrêter le cluster.
create_dataproc_cluster >> run_dataproc_hadoop >> delete_dataproc_cluster
Enfin, nous voulons que ces opérateurs s'exécutent dans un ordre particulier. Nous pouvons l'indiquer à l'aide des opérateurs de décalage de bits Python. Dans ce cas, create_dataproc_cluster s'exécutera toujours en premier, suivi de run_dataproc_hadoop et enfin de delete_dataproc_cluster.
En regroupant tout, le code se présente comme suit :
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# [START composer_hadoop_tutorial]
"""Example Airflow DAG that creates a Cloud Dataproc cluster, runs the Hadoop
wordcount example, and deletes the cluster.
This DAG relies on three Airflow variables
https://airflow.apache.org/concepts.html#variables
* gcp_project - Google Cloud Project to use for the Cloud Dataproc cluster.
* gce_zone - Google Compute Engine zone where Cloud Dataproc cluster should be
created.
* gcs_bucket - Google Cloud Storage bucket to use for result of Hadoop job.
See https://cloud.google.com/storage/docs/creating-buckets for creating a
bucket.
"""
import datetime
import os
from airflow import models
from airflow.contrib.operators import dataproc_operator
from airflow.utils import trigger_rule
# Output file for Cloud Dataproc job.
output_file = os.path.join(
models.Variable.get('gcs_bucket'), 'wordcount',
datetime.datetime.now().strftime('%Y%m%d-%H%M%S')) + os.sep
# Path to Hadoop wordcount example available on every Dataproc cluster.
WORDCOUNT_JAR = (
'file:///usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar'
)
# Arguments to pass to Cloud Dataproc job.
input_file = 'gs://pub/shakespeare/rose.txt'
wordcount_args = ['wordcount', input_file, output_file]
yesterday = datetime.datetime.combine(
datetime.datetime.today() - datetime.timedelta(1),
datetime.datetime.min.time())
default_dag_args = {
# Setting start date as yesterday starts the DAG immediately when it is
# detected in the Cloud Storage bucket.
'start_date': yesterday,
# To email on failure or retry set 'email' arg to your email and enable
# emailing here.
'email_on_failure': False,
'email_on_retry': False,
# If a task fails, retry it once after waiting at least 5 minutes
'retries': 1,
'retry_delay': datetime.timedelta(minutes=5),
'project_id': models.Variable.get('gcp_project')
}
# [START composer_hadoop_schedule]
with models.DAG(
'composer_hadoop_tutorial',
# Continue to run DAG once per day
schedule_interval=datetime.timedelta(days=1),
default_args=default_dag_args) as dag:
# [END composer_hadoop_schedule]
# Create a Cloud Dataproc cluster.
create_dataproc_cluster = dataproc_operator.DataprocClusterCreateOperator(
task_id='create_dataproc_cluster',
# Give the cluster a unique name by appending the date scheduled.
# See https://airflow.apache.org/code.html#default-variables
cluster_name='composer-hadoop-tutorial-cluster-{{ ds_nodash }}',
num_workers=2,
zone=models.Variable.get('gce_zone'),
master_machine_type='n1-standard-1',
worker_machine_type='n1-standard-1')
# Run the Hadoop wordcount example installed on the Cloud Dataproc cluster
# master node.
run_dataproc_hadoop = dataproc_operator.DataProcHadoopOperator(
task_id='run_dataproc_hadoop',
main_jar=WORDCOUNT_JAR,
cluster_name='composer-hadoop-tutorial-cluster-{{ ds_nodash }}',
arguments=wordcount_args)
# Delete Cloud Dataproc cluster.
delete_dataproc_cluster = dataproc_operator.DataprocClusterDeleteOperator(
task_id='delete_dataproc_cluster',
cluster_name='composer-hadoop-tutorial-cluster-{{ ds_nodash }}',
# Setting trigger_rule to ALL_DONE causes the cluster to be deleted
# even if the Dataproc job fails.
trigger_rule=trigger_rule.TriggerRule.ALL_DONE)
# [START composer_hadoop_steps]
# Define DAG dependencies.
create_dataproc_cluster >> run_dataproc_hadoop >> delete_dataproc_cluster
# [END composer_hadoop_steps]
# [END composer_hadoop]
5. Importer des fichiers Airflow dans Cloud Storage
Copier le DAG dans votre dossier /dags
- Commencez par ouvrir Cloud Shell, sur lequel le SDK Cloud est déjà installé.
- Clonez le dépôt d'exemples Python et accédez au répertoire composer/workflows.
git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git && cd python-docs-samples/composer/workflows
- Exécutez la commande suivante pour définir le nom de votre dossier de DAG sur une variable d'environnement.
DAGS_FOLDER=$(gcloud composer environments describe ${COMPOSER_INSTANCE} \
--location us-central1 --format="value(config.dagGcsPrefix)")
- Exécutez la commande
gsutilsuivante pour copier le code du tutoriel à l'emplacement où votre dossier /dags est créé.
gsutil cp hadoop_tutorial.py $DAGS_FOLDER
Le résultat doit se présenter comme suit :
Copying file://hadoop_tutorial.py [Content-Type=text/x-python]... / [1 files][ 4.1 KiB/ 4.1 KiB] Operation completed over 1 objects/4.1 KiB.
6. Utiliser l'interface utilisateur d'Airflow
Pour accéder à l'interface Web Airflow dans la console GCP :
|
|
Pour plus d'informations sur l'interface utilisateur d'Airflow, consultez la section Accéder à l'interface Web.
Afficher les variables
Les variables que vous avez définies précédemment sont conservées dans votre environnement. Pour les afficher, sélectionnez Admin > Variables dans la barre de menu de l'interface utilisateur Airflow.

Explorer les exécutions de DAG
Lorsque vous importez votre fichier DAG dans le dossier dags de Cloud Storage, Cloud Composer l'analyse. Si aucune erreur n'est détectée, le nom du workflow apparaît dans la liste des DAG, et le workflow est placé en file d'attente en vue d'une exécution immédiate. Pour consulter vos DAG, cliquez sur DAG en haut de la page.

Cliquez sur composer_hadoop_tutorial pour ouvrir la page d'informations sur le DAG. Cette page comprend une représentation graphique des tâches et des dépendances du workflow.

Dans la barre d'outils, cliquez sur Graph View (Vue graphique), puis passez la souris sur le graphique associé à chaque tâche pour afficher l'état des tâches. Notez que la bordure autour de chaque tâche indique également son état (bordure verte = exécution en cours, rouge = échec, etc.).

Pour exécuter de nouveau le workflow à partir de la vue graphique, procédez comme suit :
- Dans la vue graphique de l'interface utilisateur Airflow, cliquez sur le graphique
create_dataproc_cluster. - Cliquez sur Clear (Effacer) pour réinitialiser les trois tâches, puis sur OK pour confirmer.

Vous pouvez également vérifier l'état et les résultats du workflow composer-hadoop-tutorial en accédant aux pages suivantes de la console GCP :
- Clusters Cloud Dataproc pour surveiller la création et la suppression des clusters. Notez que le cluster créé par le workflow est éphémère : il n'existe que pour la durée du workflow et est supprimé dans le cadre de sa dernière tâche.
- Tâches Cloud Dataproc, pour afficher ou surveiller la tâche Apache Hadoop de décompte de mots. Cliquez sur l'ID de job pour afficher la sortie du journal associée au job.
- Navigateur Cloud Storage, pour afficher les résultats du décompte de mots dans le dossier
wordcountdu bucket Cloud Storage que vous avez créé pour cet atelier de programmation.
7. Nettoyage
Pour éviter que les ressources utilisées dans cet atelier de programmation soient facturées sur votre compte GCP :
- (Facultatif) Pour enregistrer vos données, téléchargez-les depuis le bucket Cloud Storage de l'environnement Cloud Composer et le bucket de stockage que vous avez créé pour cet atelier de programmation.
- Supprimez le bucket Cloud Storage que vous avez créé pour cet atelier de programmation.
- Supprimez le bucket Cloud Storage pour l'environnement.
- Supprimez l'environnement Cloud Composer. Notez que la suppression de votre environnement n'entraîne pas celle de son bucket de stockage.
Vous pouvez également supprimer le projet (facultatif) :
- Dans la console GCP, accédez à la page Projets.
- Dans la liste des projets, sélectionnez celui que vous souhaitez supprimer, puis cliquez sur Supprimer.
- Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.







