You are on page 1of 44
Jesús%Fernández%Bes% MLG%–%3JUN2013%%

Jesús%Fernández%Bes%

MLG%–%3JUN2013%%

Índice%%

1.Instrucciones%de%instalación% 2.Visión%general%de%Spark% 3.PySpark%

1.Uso%interacIvo% 2.Uso%standalone)

4.Uso)de)Spark)en)nuestro)cluster) 5.Integración)con)Amazon)EC2) 6.Conclusiones) 7.Referencias)

INSTRUCCIONES*DE*INSTALACIÓN*

Instrucciones%de%instalación:%

Prerrequisitos%

Java%&%Python%2.7% SCALA%(Versión%2.9)%

Descarga%Scala%2.9.2.%(Spark%necesita%Scala%para%funcionar%aunque% programemos%en%Python%pero%ATENCIÓN%“no%es%compaIble”%con%la%

úlIma%versión%de%Scala%2.10).%

h^p://www.scalaalang.org/downloads/distrib/files/scalaa2.9.2.tgz%

Descomprime%Scala%donde%desees.%%

GIT%

Por%alguna%razón,%en%Mac%OSx%necesitas%el%git.%% h^ps://code.google.com/p/gitaosxainstaller/%

Instrucciones%de%instalación:%

Instalación%de%Spark%

Descarga%la%úlIma%versión%de%Spark%(0.7)%de%la%página%web.% Versiones%anteriores%no%se%pueden%usar%con%Python.%

h^p://www.sparkaproject.org/downloadasparka0.7.0asourcesatgz%

Descomprime%Spark%donde%desees.% Ahora%es%necesario%o%bien%que%Scala%esté%en%el%path%o%mejor% fijar%una%nueva%variable%de%estado%SCALA_HOME.%

En%la%carpeta%conf%dentro%del%root%de%spark%y%crear%ahí%un%fichero% sparkBenv.sh%(se%puede%uIlizar%para%ello%la%planIlla%que%hay%en%el% directorio%sparkaenv.sh.template,%cambiándole%el%nombre%y%en%el% fichero%añadir:%

spark4env.sh*

if%[%az%"$SCALA_HOME"%]%;%then%

%%%%%SCALA_HOME=”<scalaapath>”%

%

Instrucciones%de%instalación:%

Instalación%de%Spark%(2)%

Desde%el%directorio%raiz%de%Spark%ejecuta%la%siguiente%instrucción%que%

compilará%Spark%y%descargará%lo%que%le%haga%falta%para%instalarlo.%%

 

$%sbt/sbt%package%

 

Comprobaciones%(desde%el%raiz%de%Spark)%

 

$%./sparkashell%

%cuando%se%te%abra%la%shell%interacIva%escribe%sc).%Salir%(%Ctlr%+%D%)%

 

$%./pyspark%

%al%igual%que%antes,%cuando%se%te%abra%la%shell%interacIva%escribe%sc.%Salir%(%Ctlr%+%D%)%

 

$%./run%spark.examples.SparkPi%local%

 
 

Debería%ejecutar%el%programa%de%ejemplo%que%calcula%Pi.%Deben%salir%una%serie%de%

mensajes%y%al%final%de%todo%el%valor%de%pi%(o%una%aproximación%razonable)%

 
 

%%

DESCRIPCIÓN*Y*UTILIZACIÓN*DE*

SPARK*

¿Qué%es%Spark?%

Spark%es%una%infraestructura%paralela%con:%

PrimiIvas%eficientes%para%comparIr%datos%en%memoria.%% APIs%en%Scala,%Java,%SQL%y%Python.* Aplicable%a%muchos%problemas%emergentes.%

MapReduce%simplificó%el%análisis%de%datos%en%clusters% grandes%y%sujetos%a%fallos.%Hoy%los%usuarios%quieren% más:%

Aplicaciones%más%complejas,%iteraDvas%(p.e.%Machine% learning,%graph%algorithms).% Consultas%adahoc%más%interacDvas.%% Procesado%de%flujo%en%Dempo*real.%%

Memoria%distribuida%tolerante%a%fallos%

Memoria%distribuida%tolerante%a%fallos% •   Necesario%trabajar% en%memoria%de% manera%distribuida% para%ser%eficientes.% •   Pero%entonces% ¿Como%recuperarse% ante%fallos?% % Solución:%% ****Resilient* Distributed

Necesario%trabajar% en%memoria%de% manera%distribuida% para%ser%eficientes.%

Pero%entonces% ¿Como%recuperarse% ante%fallos?%

%

Solución:%%

****Resilient*Distributed*Datasets*(RDD)*

RDDs%

Colección%parIcionada%inmutable%de%registros.% Se%crean%a%parIr%de%transformaciones%de%ficheros% Hadoop%u%otros%RDD.% Tiene%información%sobre%lineage.)Permite%recuperarse% ante%fallos.) Permite)cierto)control)sobre)el)parGcionado)y)la) persistencia.)

)

Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing

Matei Zaharia, Mosharaf Chowdhury, Tathagata Das, Ankur Dave, Justin Ma, Murphy McCauley, Michael J. Franklin, Scott Shenker, Ion Stoica

University of California, Berkeley

PrimiIvas%Spark%

Dos%Ipos.% Transformaciones:%RDD%!%RDD’%% Acciones:%RDD%!%resultado%%

TransformaDons*

AcDons*

map%

flatMap%

count%

filter%

reduce%

groupBy%

collect%

join%

(…)%

(…)%

Spark%Streaming%

Tratamiento%de%flujos%de%datos%

en%pseudo%Iempoareal.%%

Aplicaciones%|picas:%%

Procesar%Twi^er.%% EstadísIcas%%y% monitorización%de%siIos% web.%%

ObjeIvos:%

Escalable%a%grandes%clusters.) Latencias%de%segundos.% Modelo%simple%de% programación.% Integración%con%procesado% en%bloque%e%interacIvo.% Tolerancia%a%fallos.%

live%data%stream% Spark* Streaming* batches%of%X%seconds% Spark* processed%results%
live%data%stream%
Spark*
Streaming*
batches%of%X%seconds%
Spark*
processed%results%
Nueva%Abstracción:%% Dstream* Secuencia%de%RDDs%que%representa%un% flujo%de%datos.%
Nueva%Abstracción:%%
Dstream*
Secuencia%de%RDDs%que%representa%un%
flujo%de%datos.%

Shark%

Extensión%de%Hive%para% hacerlo%compaIble%con% Spark.%

Sistema%de%almacén%de% datos%(data)warehouse).%

Permite%hacer%consultas% Ipo%SQL.%

Hasta%100%%más%rápido%que% Hive.% ConverIrlos%en%RDDs.%

Usar%Spark%sobre%ellos.%

Shark% •   Extensión%de% Hive %para% hacerlo%compaIble%con% Spark .% •   Sistema%de%almacén%de% datos%( data) warehouse ).%

Programación%

API%en%Scala,%Java%y%Python:%

Originalmente%en%Scala%(Recomendado).%% En%Java%sólo%modo%standalone.%% Python%añadido%recientemente%(02/2013).%

Se%puede%uIlizar%modo%interacDvo*o*standalone.% No%se%puede%uIlizar%con%Spark%Stream%(Desarrollo% futuro).% Permite%uIlizar%cualquier%biblioteca%Python:%numpy,% scipy...%(????)%

%

PySpark:%Uso%InteracIvo%

Vamos%a%uIlizar%el%dataset%de%libros%que% uIlizamos%en%el%nltk.% Abrir%la%consola%interacIva%en%python*

$./pyspark%

% %[%$./sparkBshell))(si%quisiéramos%usar%Scala)%%]% Variable)sc%conIene%un%SparkContext.%

Objeto%que%“sabe%como%interactuar%con%el%cluster”% y%permite%crear%RDDs%a%parIr%de%ficheros%o% colecciones%de%python.%

%

Sintaxis%Python%lambda)

Python%soporta%el%definir%miniafunciones% anónimas%de%una%línea%sobre%la%marcha.%%

Originalmente%en%Lisp* *[*lambda*variable:*operación*]*

>>>)g)=)lambda)x:)x**2)))

>>>)g(3))

9)

Muy%úIl%dentro%de%filter,%map,%reduce.%%

>>>)words.filter(lambda)w:)w.startswith(‘a’)))

%

Ejercicio%Pyspark%interacIvo%

Construir%RDDs%a%parIr%de%un%fichero% (directorio)%

>>>)
>>>)

%% Ver%los%primeros%campos%del%registro%

>>>))
>>>))

%%

Ejercicio%Pyspark%interacIvo%

Construir%RDDs%a%parIr%de%un%fichero% (directorio)%

>>>)bookLines)=)sc.textFile('books'))

%% Ver%los%primeros%campos%del%registro%

>>>)bookLines.take(10))

%%

Ejercicio%Pyspark%interacIvo%

Construir%RDDs%a%parIr%de%un%fichero%(directorio)%

>>>)bookLines)=)sc.textFile('books'))

%% Ver%los%primeros%campos%del%registro%

>>>)bookLines.take(10))

Un%poco%feo,%¿no?.%Vamos%a%mostrarlo%un%poco% más%limpio%%

>>>))
>>>))

%% Otras%acciones% %%

>>>)

 

>>>)

Ejercicio%Pyspark%interacIvo%

Construir%RDDs%a%parIr%de%un%fichero%(directorio)%

>>>)bookLines)=)sc.textFile('books'))

%% Ver%los%primeros%campos%del%registro%

>>>)bookLines.take(10))

Un%poco%feo,%¿no?.%Vamos%a%mostrarlo%un%poco% más%limpio%%

>>>)for)line)in)bookLines.take(10):)print)line)

Otras%acciones%

>>>)bookLines.count())

 

>>>)bookLines.collect())

Ejercicio%Pyspark%interacIvo%(2)%

Pasar%de%lista%de%líneas%a%lista%de%palabras%para% contar%número%de%palabras.%

1.Transformar%RDD:%¿flatMap%or%map?%

>>>)
>>>)

%

2.Contar%las%palabras%

>>>)
>>>)

Ejercicio%Pyspark%interacIvo%(2)%

Pasar%de%lista%de%líneas%a%lista%de%palabras%para% contar%número%de%palabras.%

1.Transformar%RDD:%¿flatMap%or%map?%

>>>)bookWords1)=)bookLines.map()lambda)x:)x.split(')'))))

>>>)bookWords1.take(50))

>>>)

%

2.Contar%las%palabras%

>>>)
>>>)

Ejercicio%Pyspark%interacIvo%(2)%

Pasar%de%lista%de%líneas%a%lista%de%palabras%para% contar%número%de%palabras.%

1.Transformar%RDD:%¿flatMap%or%map?%

>>>)bookWords1)=)bookLines.map()lambda)x:)x.split(')'))))

>>>)bookWords1.take(50))

>>>)bookWords2)=)bookLines.flatMap()lambda)x:)x.split(')'))))

>>>)bookWords2.take(50)))

>>>)bookWords2.take(50)))

>>>)

%

2.Contar%las%palabras%

>>>)
>>>)

Ejercicio%Pyspark%interacIvo%(2)%

Pasar%de%lista%de%líneas%a%lista%de%palabras%para% contar%número%de%palabras.%

1.Transformar%RDD:%¿flatMap%or%map?%

>>>)bookWords1)=)bookLines.map()lambda)x:)x.split(')'))))

>>>)bookWords1.take(50))

>>>)bookWords2)=)bookLines.flatMap()lambda)x:)x.split(')'))))

>>>)bookWords2.take(50)))

>>>)bookWords2.take(50)))

>>>)bookWords)=)bookWords2.cache())

>>>)bookWords2.take(50)))

%

2.Contar%las%palabras%

>>>)
>>>)

Ejercicio%Pyspark%interacIvo%(2)%

Pasar%de%lista%de%líneas%a%lista%de%palabras%para% contar%número%de%palabras.%

1.Transformar%RDD:%¿flatMap%or%map?%

>>>)bookWords1)=)bookLines.map()lambda)x:)x.split(')'))))

>>>)bookWords1.take(50))

>>>)bookWords2)=)bookLines.flatMap()lambda)x:)x.split(')'))))

>>>)bookWords2.take(50)))

>>>)bookWords2.take(50)))

>>>)bookWords)=)bookWords2.cache())

>>>)bookWords2.take(50)))

%

2.Contar%las%palabras%

>>>)bookWords.count()*

MapReduce%en%Spark*

map(%func%)%

Return%a%new%distributed%dataset%formed%by%passing%each%element%of%the%source%through%a%

funcIon%func.%

flatMap(%func%)%

Similar%to%map,%but%each%input%item%can%be%mapped%to%0%or%more%output%items%(so%func%should%

return%a%Seq%rather%than%a%single%item).%

reduce(%func%)%

Aggregate%the%elements%of%the%dataset%using%a%funcIon%func%(which%takes%two%arguments%and%

returns%one).%The%funcIon%should%be%commutaIve%and%associaIve%so%that%it%can%be%computed%

correctly%in%parallel.%

reduceByKey(%func%)%

When%called%on%a%dataset%of%(K,%V)%pairs,%returns%a%dataset%of%(K,%V)%pairs%where%the%values%for%

each%key%are%aggregated%using%the%given%reduce%funcIon.%%

Ejercicio%Pyspark%interacIvo%(3)%

Calcula%la%línea%de%longitud%máxima%

 

>>>)

Muestra%las%líneas%de%longitud%máxima%

 

>>>)

Ejercicio%Pyspark%interacIvo%(3)%

Calcula%la%línea%de%longitud%máxima%

 

>>>%linesMax%=%bookLines.map(lambda%line:%len(line.split('%'))).reduce(max)%

Muestra%las%líneas%de%longitud%máxima%

 

>>>)

Ejercicio%Pyspark%interacIvo%(3)%

Calcula%la%línea%de%longitud%máxima%

 

>>>%linesMax%=%bookLines.map(lambda%line:%len(line.split('%'))).reduce(max)%

Muestra%las%líneas%de%longitud%máxima%

 

>>>%bookLines.filter(lambda%line:%len(line.split('%'))==linesMax%).collect()%

Ejercicio%Pyspark%%interacIvo%(4)%

Más%MapReduce:%WordCount* A%parIr%de%los%ficheros%extraer%pares% (palabra,conteo)% En%primer%lugar%hace%falta%importar%el%operador% suma%para%usarlo%de%manera%funcional%

 

>>>%from%operator%import%add%

A%parIr%del%RDD%de%palabras%bookWords:%

1.Map:%Generar%pares%(w,1)% 2.Reduce:%sumar%aquellos%pares%que%tengan%la%misma% clave%w.%

Ejercicio%Pyspark%interacIvo%(4)%

1.Map:%Generar%pares%(w,1)% 2.Reduce:%sumar%aquellos%pares%que%tengan%la%misma% clave%w.%

>>>)
>>>)

%%

Mostrar%el%resultado%

>>>)
>>>)

%%

Ejercicio%Pyspark%interacIvo%(4)%

1.Map:%Generar%pares%(w,1)% 2.Reduce:%sumar%aquellos%pares%que%tengan%la%misma% clave%w.%

>>>)counts)=)bookWords.map(lambda)x:)(x,1))).reduceByKey(add).cache())

%%

Mostrar%el%resultado%

>>>)
>>>)

%%

Ejercicio%Pyspark%interacIvo%(4)%

1.Map:%Generar%pares%(w,1)% 2.Reduce:%sumar%aquellos%pares%que%tengan%la%misma% clave%w.%

>>>)counts)=)bookWords.map(lambda)x:)(x,1))).reduceByKey(add).cache())

%%

Mostrar%el%resultado%

>>>)counts.take(50))

>>>)for)pair)in)counts.take(50):)print)pair)

%%

Modo%standalone)

Hacer%programas%completos%que%se%

ejecuten%en%bloque%en%lugar%de%en%la%

consola%interacIva.%

 
 

Invocación%|pica:%

./pyspark)file.py)<master>)<inputs>)

[En%local%./pyspark)file.py)local)

<inputs>)]%

 

Dos%pasos%básicos:% 1.importar%SparkContext% %from)pyspark)import)SparkContext) 2.Crear%SparkContext%en%el%nodo% master,%nombre%del%trabajo%[y% dependencias%que%hay%que%enviar%al% nodo.]%

sc)=)SparkContext(sys.argv[1],)”job",)

pyFiles=['MyFile.py',)'lib.zip',)'app.egg’]))

%

wordCount.py*

import%sys%

from%operator%import%add%

from%pyspark%import%SparkContext%

if% __name__%==%"__main__":%

%%%%if%len(sys.argv)%<%3:%

%%%%%%%%print%>>%sys.stderr,%\% %%%%%%%%%%%%"Usage:%PythonWordCount%<master>%<file>"%

%%%%%%%%exit(a1)%

%%%%sc%=%SparkContext(sys.argv[1],%"PythonWordCount")%

%%%%lines%=%sc.textFile(sys.argv[2],%1)%

%%%%% %%%%counts%=%lines.flatMap(lambda%x:%x.split('%'))%\%

%%%%%%%%%%%%%%%%%%.map(lambda%x:%(x,%1))%\%

%%%%%%%%%%%%%%%%%%.reduceByKey(add)%

%%%%output%=%counts.collect()%

%%%%for%(word,%count)%in%output:%

%%%%%%%%print%"%s%:%%i"%%%(word,%count)%

Ejemplos%ML%de%Pyspark%

Junto%con%Spark%hay%una%serie%de%ejemplos%de% algoritmos%Ipo%ML:%

Regresión%LogísIca% Kmeans% ALS%%

Tienen%graves%errores%de%programación%

logisDc_regression.py*

for%i%in%range(1,%ITERATIONS%+%1):%

%%%%%%%%print%"On%iteraIon%%i"%%%i%

% %%%%%%%%gradient%=%points.map(lambda%p:%

%%%%%%%%%%%%(1.0%/%(1.0%+%exp(ap.y%*%np.dot(w,%p.x))))%*%p.y%*%p.x%).reduce(add)%

%%%%%%%%w%a=%gradient%

Spark%en%nuestro%cluster%

Funciona%sobre%el%gestor%de%recursos%Apache) Mesos.)CompaIble%con%Hadoop.% Dos)formas)principales)de)acceder:)

Ordenador%personal.%Necesario%instalar% bibliotecas%Mesos.%

h^p://sparkaproject.org/docs/latest/runningaonamesos.html%

Conectándose%primero%a%amaterasu.tsc.uc3m.es).)) %

Spark%en%nuestro%cluster%(2)%

Conectarse%a%amaterasu%

ssh%amaterasu%

Invocar%desde%allí%Spark%en%el%masternode%del%cluster:%

10.0.12.18:5050*o%subserver1.tsc.uc3m.es:5050%

Modo*interacDvo:*

*MASTER=mesos://10.0.12.18:5050%/usr/local/spark/pyspark%

Modo*standalone:*

Introducir%mesos://10.0.12.18:5050%como%primer%parámetro%del% programa:%

%/usr/local/spark/pyspark%example.py%mesos://10.0.12.18:5050%

<params>%

Ver%trabajos%acIvos%a>%En%el%navegador%10.0.12.18:5050%%

%

Spark%en%nuestro%cluster%(3)%

¿cómo%subir%ficheros%para%procesar%y%descargar%resultados?%

UIlizar%el%Hadoop)FileSystem)(HDFS).%

Ver%instrucciones%de%Harold%%

h^p://www.tsc.uc3m.es/~miguel/MLG/adjuntos/Hadoop.pdf%%(pag.%12)%

$)hadoop)BBconfig)conf_cluster)dfs)Bput)src)dest)

$)hadoop)BBconfig)conf_cluster)dfs)BcopyFromLocal)src)dest)

$)hadoop)BBconfig)conf_cluster)dfs)Bget)src)dest)

$)hadoop)BBconfig)conf_cluster)dfs)–copyToLocal)

A%la%hora%de%leer%el%fichero%uIlizar%la%URL%correcta:%%

sc.textFile(‘hdfs://<namenode>:8080/path‘)%

UIlizar%un%directorio%accesible%desde%el%cluster:%

/export/clusterdata)

%

•   Spark%provee%de%scripts%para%lanzar%trabajos% directamente%a%Amazon%ElasIc%Compute%Cloud% (EC2).%% •   Configuran%automáIcamente )Mesos ,) Spark)y) HDFS) en%el%clúster%para%I.% •   Necesario%pagar%Computo%($0,260%por%instancia/

Spark%provee%de%scripts%para%lanzar%trabajos% directamente%a%Amazon%ElasIc%Compute%Cloud%

(EC2).%%

Configuran%automáIcamente)Mesos,)Spark)y) HDFS)en%el%clúster%para%I.% Necesario%pagar%Computo%($0,260%por%instancia/ hora%para%la%predeterminada)%+%Coste%de% almacenamiento.% Muy%fácil%de%usar.%% Grants!!!!%h^p://aws.amazon.com/es/grants/%

•   Registrarse%en%Amazon%Web% Services %(AWS).% –   h^p://aws.amazon.com/es/ % •   En%la%pantalla%principal%de%mi% cuenta%crear%Claves%de%acceso.% –   Pestaña%de%credenciales%de%

Registrarse%en%Amazon%Web% Services%(AWS).%

h^p://aws.amazon.com/es/%

En%la%pantalla%principal%de%mi% cuenta%crear%Claves%de%acceso.%

Pestaña%de%credenciales%de% seguridad.%

Antes%de%ejecutar%los%scripts% Ienes%que%fijar%las%variables%de% estado%AWS_ACCESS_KEY_ID%y% AWS_SECRET_ACCESS_KEY_ID%a% esos%valores.%

•   Registrarse%en%Amazon%Web% Services %(AWS).% –   h^p://aws.amazon.com/es/ % •   En%la%pantalla%principal%de%mi% cuenta%crear%Claves%de%acceso.% –   Pestaña%de%credenciales%de%
•   Generar%par%de%claves:% –   Accede%a%tu%consola% AWS%y%allí%entra%en%el% servicio%EC2.% –   Selecciona% keyPairs %y% crea%el%par% –  

Generar%par%de%claves:%

Accede%a%tu%consola% AWS%y%allí%entra%en%el%

servicio%EC2.%

Selecciona%keyPairs%y% crea%el%par%

Descargate%el%fichero%y% en%Linux/Mac%Osx% cambiale%los%permiso%a%

600%

chmod%600% mykey.pem%

%

•   Generar%par%de%claves:% –   Accede%a%tu%consola% AWS%y%allí%entra%en%el% servicio%EC2.% –   Selecciona% keyPairs %y% crea%el%par% –  
•   En%el%directorio% ec2 %de% spark%está%el%script%para% lanzar%un%cluster,%acceder,%pararlo,%destruirlo.% >>>%./sparkaec2%ak%<keypair>%ai%< key afile>%as%<numaslaves>%launch %< clusteraname>% >>>%./sparkaec2%ak%<keypair>%ai%< key afile>% login

En%el%directorio%ec2%de%spark%está%el%script%para% lanzar%un%cluster,%acceder,%pararlo,%destruirlo.%

>>>%./sparkaec2%ak%<keypair>%ai%<keyafile>%as%<numaslaves>%launch%<clusteraname>%

>>>%./sparkaec2%ak%<keypair>%ai%<keyafile>%login%<clusteraname>%

>>>%./sparkaec2%ak%<keypair>%ai%<keyafile>%as%<numaslaves>%launch%<clusteraname>%

>>>%./sparkaec2%destroy%<clusteraname>%

Más%detalles%aquí.%

h^p://sparkaproject.org/docs/latest/ec2ascripts.html%

%

Conclusiones%

Spark%propone%una%infraestructura%interesante%para% computación%en%clusters.%

API%Scala,%Python%y%Java%sencillo.%Sobretodo%para%gente% que%sabe%programación%funcional.% Modo%interacIvo%(extracción%rápida%de%caracterísIcas%de% grandes%datasets).% Funciona%bien%con%procesado%secuencial.% Fácil%de%usar%en%EC2.%

Muy%(demasiado)%nuevo.%Python%en%febrero%de%este% año.%%

No%existe%una%comunidad%de%Machine%Learning%como% Mahout%en%torno%a%Spark.%

Algunas%Referencias%

Documentación%oficial%de%Spark%

h^p://sparkaproject.org/docs/latest/index.html%

MiniaCurso%del%AMPLAB%sobre%BigData:%Spark,%Shark%y%EC2.%

h^p://ampcamp.berkeley.edu/bigadataaminiacourse/%

%

Algunos%vídeos%con%presentaciones%de%las%desarrolladores.%

NIPS%2011:%%

h^ps://www.youtube.com/watch?v=qLvLgasqxKc%

Berkley%Analizing%Big%Data%with%twi^er%(lesson%16):%

%h^ps://www.youtube.com/watch?v=rpXxsp1vSEs%

PyData%2013:%

%h^p://vimeo.com/63274414%

%%

Libro%de%O’Reilly%en%camino%(Agosto%de%2013):%

h^p://shop.oreilly.com/product/0636920028512.do%