├── .gitignore ├── ansible ├── bootstrap.yml └── roles │ ├── spark │ ├── files │ │ ├── common.sh │ │ ├── start-master.sh │ │ ├── start-slave.sh │ │ ├── start-jupyter.sh │ │ └── start-zeppelin.sh │ └── tasks │ │ └── main.yml │ ├── volume │ ├── tasks │ │ └── main.yml │ └── files │ │ └── mount-volume.sh │ ├── hdfs │ ├── files │ │ ├── start-datanode.sh │ │ ├── volume-directories.sh │ │ ├── set-properties.sh │ │ ├── start-namenode.sh │ │ └── user-directories.sh │ └── tasks │ │ └── main.yml │ └── consul │ ├── files │ ├── join-cluster.sh │ ├── join-cluster-wan.sh │ ├── set-hostname.sh │ ├── start-agent.sh │ └── start-server-agent.sh │ └── tasks │ └── main.yml ├── packer ├── provisioners │ ├── install-zeppelin.sh │ ├── install-jupyter.sh │ ├── install-docker.sh │ ├── install-ansible.sh │ ├── install-spark.sh │ ├── install-consul.sh │ ├── core-site.xml │ ├── common.sh │ ├── hdfs-site.xml │ └── install-hdfs.sh ├── packer-export.sh.tmplt └── build.json ├── terraform ├── master │ ├── bootstrap.sh │ └── main.tf ├── worker │ ├── bootstrap.sh │ └── main.tf └── main.tf ├── secondary-dc └── main.tf ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | */*.tfvars 2 | */terraform.tfstate 3 | */terraform.tfstate.backup 4 | */.terraform 5 | packer/packer-export.sh 6 | -------------------------------------------------------------------------------- /ansible/bootstrap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | roles: 4 | - volume 5 | - consul 6 | - hdfs 7 | - spark 8 | -------------------------------------------------------------------------------- /ansible/roles/spark/files/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo sh -c "echo spark.rpc=$SPARK_RPC > /opt/spark/default/conf/spark-defaults.conf" 4 | -------------------------------------------------------------------------------- /ansible/roles/volume/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: mount volume 3 | script: mount-volume.sh 4 | tags: 5 | - master 6 | - worker 7 | - block_storage 8 | -------------------------------------------------------------------------------- /ansible/roles/spark/files/start-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting Spark master 4 | cd /opt/spark/default 5 | sudo -u ubuntu SPARK_PUBLIC_DNS=$(hostname) sbin/start-master.sh -h $(hostname) 6 | -------------------------------------------------------------------------------- /ansible/roles/hdfs/files/start-datanode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting HDFS datanode 4 | sudo -u hduser /opt/hadoop/default/sbin/hadoop-daemon.sh \ 5 | --config /opt/hadoop/default/etc/hadoop \ 6 | start datanode 7 | -------------------------------------------------------------------------------- /ansible/roles/spark/files/start-slave.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting Spark slave 4 | cd /opt/spark/default 5 | sudo -u ubuntu SPARK_PUBLIC_DNS=$(hostname) sbin/start-slave.sh -h $(hostname) spark://$SPARK_MASTER_HOST:7077 6 | -------------------------------------------------------------------------------- /ansible/roles/hdfs/files/volume-directories.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Creating HDFS directories 4 | sudo mkdir -p /mnt/volume/hdfs/namenode 5 | sudo mkdir -p /mnt/volume/hdfs/datanode 6 | sudo chown hduser:hadoop -R /mnt/volume/hdfs 7 | -------------------------------------------------------------------------------- /packer/provisioners/install-zeppelin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Pulling dylanmei/zeppelin docker image..." 7 | sudo docker pull dylanmei/zeppelin:0.7.1 8 | -------------------------------------------------------------------------------- /ansible/roles/consul/files/join-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Waiting for Consul agent to start... 4 | until nc -z localhost 8400; do 5 | sleep 10 6 | done 7 | 8 | #Joining the Consul cluster... 9 | consul join $CONSUL_SERVER_IP 10 | -------------------------------------------------------------------------------- /packer/provisioners/install-jupyter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Pulling jupyter/all-spark-notebook docker image..." 7 | sudo docker pull jupyter/all-spark-notebook:c33a7dc0eece 8 | -------------------------------------------------------------------------------- /ansible/roles/consul/files/join-cluster-wan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Waiting for Consul agent to start... 4 | until nc -z localhost 8400; do 5 | sleep 10 6 | done 7 | 8 | #Joining the Consul cluster... 9 | consul join -wan $CONSUL_PRIMARY_SERVER_IP 10 | -------------------------------------------------------------------------------- /ansible/roles/consul/files/set-hostname.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Setting hostname 4 | echo "$(hostname).node.$CONSUL_DC_NAME.consul" > /etc/hostname #setting consul hostname 5 | sudo service hostname restart 6 | sudo sh -c "echo \"$(hostname -I | awk '{print $1;}') $(hostname)\" >> /etc/hosts" 7 | -------------------------------------------------------------------------------- /ansible/roles/hdfs/files/set-properties.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo -u hduser sed -i -e "s//$HDFS_NAMENODE_HOST/g" /opt/hadoop/default/etc/hadoop/core-site.xml 4 | sudo -u hduser sed -i -e "s//$HDFS_BLOCK_SIZE/g" /opt/hadoop/default/etc/hadoop/hdfs-site.xml 5 | -------------------------------------------------------------------------------- /ansible/roles/volume/files/mount-volume.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Wait for volume to be attached 4 | while [ ! -e /dev/vdb ]; do 5 | sleep 10 6 | done 7 | 8 | # Mounting attached volume 9 | mkfs.ext4 /dev/vdb 10 | mkdir /mnt/volume 11 | mount /dev/vdb /mnt/volume 12 | chown -R ubuntu /mnt/volume 13 | -------------------------------------------------------------------------------- /ansible/roles/hdfs/files/start-namenode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Format namenode 4 | sudo -u hduser /opt/hadoop/default/bin/hadoop namenode -format 5 | 6 | #Starting HDFS namenode 7 | sudo -u hduser /opt/hadoop/default/sbin/hadoop-daemon.sh \ 8 | --config /opt/hadoop/default/etc/hadoop \ 9 | start namenode 10 | -------------------------------------------------------------------------------- /ansible/roles/consul/files/start-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting consul server agent 4 | sudo daemon -- consul agent \ 5 | -data-dir /tmp/consul \ 6 | -node=$(hostname) \ 7 | -config-dir /etc/consul.d \ 8 | -bind $(hostname -I | awk '{print $1;}') \ 9 | -dc $CONSUL_DC_NAME \ 10 | > /var/log/consul/consul.log 11 | -------------------------------------------------------------------------------- /packer/provisioners/install-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Installing apparmor..." 7 | sudo apt-get install -y apparmor 8 | 9 | echo "Installing docker..." 10 | sudo apt-get install -y docker-engine 11 | 12 | echo "Add ubuntu to docker group..." 13 | sudo gpasswd -a ubuntu docker 14 | -------------------------------------------------------------------------------- /ansible/roles/consul/files/start-server-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting consul server agent 4 | sudo daemon -- consul agent -server \ 5 | -bootstrap-expect 1 \ 6 | -data-dir /tmp/consul \ 7 | -node=$(hostname) \ 8 | -config-dir /etc/consul.d \ 9 | -bind $(hostname -I | awk '{print $1;}') \ 10 | -dc $CONSUL_DC_NAME \ 11 | > /var/log/consul/consul.log 12 | -------------------------------------------------------------------------------- /packer/provisioners/install-ansible.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Installing ansible..." 7 | sudo pip install ansible==2.2.0.0 8 | 9 | echo "Moving SparkNow playbooks to /var/local/playbooks..." 10 | sudo mv /tmp/playbooks /var/local/playbooks 11 | 12 | echo "Installing daemon..." 13 | sudo apt-get install daemon 14 | -------------------------------------------------------------------------------- /terraform/master/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ansible-playbook ${ansible_opt} \ 4 | --tags ${ansible_tags} \ 5 | -e "consul_dc_name=${dc_name}" \ 6 | -e "hdfs_namenode_host=$(hostname).node.${dc_name}.consul" \ 7 | -e "spark_rpc=${spark_rpc}" \ 8 | -e "hdfs_block_size=${hdfs_block_size}" \ 9 | -e "zeppelinhub_api_token=${zeppelinhub_api_token}" \ 10 | /var/local/playbooks/bootstrap.yml 11 | -------------------------------------------------------------------------------- /terraform/worker/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ansible-playbook ${ansible_opt} \ 4 | --tags ${ansible_tags} \ 5 | -e "consul_dc_name=${dc_name}" \ 6 | -e "hdfs_namenode_host=${master_ip}" \ 7 | -e "consul_server_ip=${master_ip}" \ 8 | -e "spark_master_host=${spark_master_host}" \ 9 | -e "spark_rpc=${spark_rpc}" \ 10 | -e "hdfs_block_size=${hdfs_block_size}" \ 11 | /var/local/playbooks/bootstrap.yml 12 | -------------------------------------------------------------------------------- /ansible/roles/hdfs/files/user-directories.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Creating HDFS user directories 4 | sudo -u hduser /opt/hadoop/default/bin/hadoop fs -mkdir /jupyter 5 | sudo -u hduser /opt/hadoop/default/bin/hadoop fs -chown jovyan:ubuntu /jupyter 6 | sudo -u hduser /opt/hadoop/default/bin/hadoop fs -mkdir /ubuntu 7 | sudo -u hduser /opt/hadoop/default/bin/hadoop fs -chown ubuntu:ubuntu /ubuntu 8 | sudo -u hduser /opt/hadoop/default/bin/hadoop fs -mkdir /root 9 | sudo -u hduser /opt/hadoop/default/bin/hadoop fs -chown root:root /root 10 | -------------------------------------------------------------------------------- /ansible/roles/spark/files/start-jupyter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting Jupyter notebook 4 | mkdir /mnt/volume/juputer-workspace 5 | chown -R ubuntu /mnt/volume/ 6 | sudo docker run -d \ 7 | --net=host \ 8 | --pid=host \ 9 | -v /mnt/volume/juputer-workspace:/home/jovyan/work \ 10 | -v /mnt/volume/:/mnt/volume \ 11 | -v /opt/spark/default:/usr/local/spark \ 12 | -v $(readlink /opt/hadoop/default):/opt/hadoop/default \ 13 | -e TINI_SUBREAPER=true \ 14 | -e SPARK_OPTS="--master=spark://$(hostname):7077 --conf spark.rpc=$SPARK_RPC" \ 15 | jupyter/all-spark-notebook:c33a7dc0eece 16 | -------------------------------------------------------------------------------- /ansible/roles/hdfs/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: set properties 3 | script: set-properties.sh 4 | environment: 5 | HDFS_NAMENODE_HOST: "{{ hdfs_namenode_host }}" 6 | HDFS_BLOCK_SIZE: "{{ hdfs_block_size }}" 7 | tags: 8 | - master 9 | - worker 10 | 11 | - name: create volume directories 12 | script: volume-directories.sh 13 | tags: 14 | - master 15 | - worker 16 | 17 | - name: start namenode 18 | script: start-namenode.sh 19 | tags: 20 | - master 21 | 22 | - name: start datanode 23 | script: start-datanode.sh 24 | tags: 25 | - worker 26 | 27 | - name: create user directories 28 | script: user-directories.sh 29 | tags: 30 | - master 31 | -------------------------------------------------------------------------------- /ansible/roles/spark/files/start-zeppelin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Starting Zeppelin 4 | 5 | sudo docker run -d \ 6 | -v /mnt/volume/zeppelin-workspace:/zeppelin-workspace \ 7 | -v /var/run/docker.sock:/var/run/docker.sock \ 8 | --net=host \ 9 | -e MASTER=spark://$(hostname):7077 \ 10 | -e ZEPPELIN_PORT=9999 \ 11 | -e ZEPPELIN_NOTEBOOK_DIR=/zeppelin-workspace \ 12 | -e SPARK_SUBMIT_OPTIONS="--conf spark.rpc=$SPARK_RPC" \ 13 | -e ZEPPELIN_NOTEBOOK_STORAGE="org.apache.zeppelin.notebook.repo.GitNotebookRepo, org.apache.zeppelin.notebook.repo.zeppelinhub.ZeppelinHubRepo" \ 14 | -e ZEPPELINHUB_API_ADDRESS="https://www.zepl.com" \ 15 | -e ZEPPELINHUB_API_TOKEN="$ZEPPELINHUB_API_TOKEN" \ 16 | dylanmei/zeppelin:0.7.1 17 | -------------------------------------------------------------------------------- /packer/packer-export.sh.tmplt: -------------------------------------------------------------------------------- 1 | export SPARK_DOWNLOAD_URL="http://d3kbcqa49mib13.cloudfront.net/spark-2.1.0-bin-hadoop2.7.tgz" 2 | # you can change the download URL if you need another Spark version, everything should work 3 | # as long as the binary is compatible with Hadoop 2.7 4 | 5 | export PACKER_IMAGE_NAME="SparkNow_spark-2.1.0-hadoop2.7" 6 | export PACKER_SOURCE_IMAGE_NAME="Ubuntu 14.04" #this may be different in your OpenStack tenancy 7 | export PACKER_NETWORK="" # your OpenStack tenancy private network id 8 | export PACKER_FLAVOR="" # the instance flavor that you want to use to build SparkNow 9 | export PACKER_AVAILABILITY_ZONE="" # an availability zone name in your OpenStack tenancy 10 | export PACKER_FLOATING_IP_POOL="" # a floating IP pool in your OpenStack tenancy 11 | -------------------------------------------------------------------------------- /packer/provisioners/install-spark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Fetching $SPARK_DOWNLOAD_URL..." 7 | SPARK_TGZ=${SPARK_DOWNLOAD_URL##*/} 8 | SPARK_PACKAGE_NAME=${SPARK_TGZ%.*} 9 | wget -q $SPARK_DOWNLOAD_URL -O /tmp/$SPARK_TGZ 10 | 11 | echo "Installing $SPARK_PACKAGE_NAME..." 12 | sudo mkdir /opt/spark/ 13 | sudo tar xzf /tmp/$SPARK_TGZ -C /opt/spark/ 14 | sudo ln -s /opt/spark/$SPARK_PACKAGE_NAME /opt/spark/default 15 | sudo chown -R ubuntu:ubuntu /opt/spark 16 | 17 | echo "Setting HADOOP_CONF_DIR..." 18 | sudo echo "export HADOOP_CONF_DIR=/opt/hadoop/default/etc/hadoop" > /opt/spark/default/conf/spark-env.sh 19 | sudo chmod +x /opt/spark/default/conf/spark-env.sh 20 | 21 | echo "$SPARK_PACKAGE_NAME installation complete." 22 | -------------------------------------------------------------------------------- /packer/provisioners/install-consul.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Fetching Consul..." 7 | CONSUL_DOWNLOAD_URL=https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip 8 | CONSUL_ZIP=${CONSUL_DOWNLOAD_URL##*/} 9 | CONSUL_PACKAGE_NAME=${CONSUL_ZIP%.*} 10 | wget -q $CONSUL_DOWNLOAD_URL -O /tmp/$CONSUL_ZIP 11 | 12 | echo "Installing $CONSUL_PACKAGE_NAME..." 13 | 14 | sudo mkdir /opt/consul/ 15 | sudo unzip /tmp/$CONSUL_ZIP -d /usr/bin 16 | sudo mkdir /tmp/consul 17 | sudo mkdir /etc/consul.d 18 | sudo mkdir /var/log/consul 19 | sudo sh -c 'echo "{\"ports\": {\"dns\": 53}, \"recursor\": \"8.8.8.8\"}" > /etc/consul.d/conf.json' 20 | sudo sh -c "echo \"nameserver 127.0.0.1\" > /etc/resolvconf/resolv.conf.d/head" 21 | 22 | echo "$CONSUL_PACKAGE_NAME installation complete." 23 | -------------------------------------------------------------------------------- /ansible/roles/consul/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: start consul server agent 3 | script: start-server-agent.sh 4 | environment: 5 | CONSUL_DC_NAME: "{{ consul_dc_name }}" 6 | tags: 7 | - master 8 | 9 | - name: start consul agent 10 | script: start-agent.sh 11 | environment: 12 | CONSUL_DC_NAME: "{{ consul_dc_name }}" 13 | tags: 14 | - worker 15 | 16 | - name: join consul cluster 17 | script: join-cluster.sh 18 | environment: 19 | CONSUL_SERVER_IP: "{{ consul_server_ip }}" 20 | tags: 21 | - worker 22 | 23 | - name: join consul cluster (wan) 24 | script: join-cluster-wan.sh 25 | environment: 26 | CONSUL_PRIMARY_SERVER_IP: "{{ consul_primary_server_ip }}" 27 | tags: 28 | - join-wan 29 | 30 | - name: set hostname 31 | script: set-hostname.sh 32 | environment: 33 | CONSUL_DC_NAME: "{{ consul_dc_name }}" 34 | tags: 35 | - master 36 | - worker 37 | -------------------------------------------------------------------------------- /ansible/roles/spark/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Spark common 3 | script: common.sh 4 | environment: 5 | SPARK_RPC: "{{ spark_rpc }}" 6 | tags: 7 | - master 8 | - worker 9 | 10 | - name: start Spark master 11 | script: start-master.sh 12 | tags: 13 | - master 14 | - start-spark-master 15 | 16 | - name: start Spark slave 17 | script: start-slave.sh 18 | environment: 19 | SPARK_MASTER_HOST: "{{ spark_master_host }}" 20 | tags: 21 | - worker 22 | 23 | - name: start Jupyter notebook 24 | script: start-jupyter.sh 25 | environment: 26 | SPARK_RPC: "{{ spark_rpc }}" 27 | tags: 28 | - master 29 | - start-spark-master 30 | 31 | - name: start Zeppelin 32 | script: start-zeppelin.sh 33 | environment: 34 | SPARK_RPC: "{{ spark_rpc }}" 35 | ZEPPELINHUB_API_TOKEN: "{{ zeppelinhub_api_token }}" 36 | tags: 37 | - master 38 | - start-spark-master 39 | -------------------------------------------------------------------------------- /packer/provisioners/core-site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | 20 | 21 | fs.default.name 22 | hdfs://:9000 23 | 24 | 25 | -------------------------------------------------------------------------------- /packer/provisioners/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Updating package info..." 7 | 8 | #Docker 9 | sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D 10 | sudo sh -c 'echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" > /etc/apt/sources.list.d/docker.list' 11 | 12 | sudo apt-get update -y 13 | 14 | echo "Upgrading packages..." 15 | sudo apt-get upgrade -y 16 | 17 | echo "Installing java..." 18 | sudo apt-get install -y openjdk-7-jre-headless 19 | echo "export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64" >> ~/.bashrc 20 | 21 | echo "Installing python packages..." 22 | sudo apt-get install -y \ 23 | python-scipy \ 24 | python-pip \ 25 | python-dev \ 26 | build-essential \ 27 | python-crypto \ 28 | libffi-dev \ 29 | libssl-dev 30 | 31 | echo "Installing unzip..." 32 | sudo apt-get install -y unzip 33 | 34 | echo "Increase open files limit" 35 | sudo sh -c \ 36 | 'echo "* soft nofile 10240" >> /etc/security/limits.conf' 37 | sudo sh -c \ 38 | 'echo "* hard nofile 10240" >> /etc/security/limits.conf' 39 | -------------------------------------------------------------------------------- /packer/provisioners/hdfs-site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | 20 | 21 | dfs.replication 22 | 3 23 | 24 | 25 | dfs.namenode.name.dir 26 | file:/mnt/volume/hdfs/namenode 27 | 28 | 29 | dfs.datanode.data.dir 30 | file:/mnt/volume/hdfs/datanode 31 | 32 | 33 | dfs.block.size 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /secondary-dc/main.tf: -------------------------------------------------------------------------------- 1 | variable dc_name { default = "dc2" } 2 | variable keypair_name { } 3 | variable cluster_prefix { } 4 | variable floating_ip_pool { } 5 | variable SparkNow_image_name { } 6 | variable master_flavor_name { } 7 | variable worker_flavor_name { } 8 | variable worker_count { } 9 | variable master_volume_size { } 10 | variable worker_volume_size { } 11 | variable primary_master_ip { } 12 | variable spark_master_host { } 13 | 14 | module "master_instance" { 15 | source = "../terraform/master" 16 | name_prefix = "${var.cluster_prefix}" 17 | floating_ip_pool = "${var.floating_ip_pool}" 18 | image_name = "${var.SparkNow_image_name}" 19 | flavor_name = "${var.master_flavor_name}" 20 | keypair_name = "${var.keypair_name}" 21 | volume_size = "${var.master_volume_size}" 22 | dc_name = "${var.dc_name}" 23 | ansible_opt = "--skip-tags start-spark-master -e consul_primary_server_ip=${var.primary_master_ip}" 24 | ansible_tags = "master,join-wan" 25 | } 26 | 27 | module "worker_instances" { 28 | source = "../terraform/worker" 29 | name_prefix = "${var.cluster_prefix}" 30 | image_name = "${var.SparkNow_image_name}" 31 | flavor_name = "${var.worker_flavor_name}" 32 | keypair_name = "${var.keypair_name}" 33 | master_ip = "${module.master_instance.ip_address}" 34 | count = "${var.worker_count}" 35 | volume_size = "${var.worker_volume_size}" 36 | dc_name = "${var.dc_name}" 37 | spark_master_host = "${var.spark_master_host}" 38 | } 39 | -------------------------------------------------------------------------------- /terraform/worker/main.tf: -------------------------------------------------------------------------------- 1 | variable dc_name { } 2 | variable name_prefix {} 3 | variable image_name {} 4 | variable flavor_name {} 5 | variable keypair_name {} 6 | variable master_ip {} 7 | variable count {} 8 | variable volume_size {} 9 | variable volume_device { default = "/dev/vdb" } 10 | variable ansible_opt { default = "" } 11 | variable ansible_tags { default = "worker" } 12 | variable spark_rpc {} 13 | variable spark_master_host {} 14 | variable hdfs_block_size {} 15 | variable network_name {} 16 | 17 | resource "openstack_blockstorage_volume_v1" "blockstorage" { 18 | name = "${var.name_prefix}-worker-volume-${format("%03d", count.index)}" 19 | size = "${var.volume_size}" 20 | count = "${var.count}" 21 | } 22 | 23 | resource "template_file" "bootstrap" { 24 | template = "${file("${path.module}/bootstrap.sh")}" 25 | vars { 26 | dc_name = "${var.dc_name}" 27 | master_ip = "${var.master_ip}" 28 | ansible_opt = "${var.ansible_opt}" 29 | ansible_tags = "${var.ansible_tags}" 30 | spark_rpc = "${var.spark_rpc}" 31 | spark_master_host = "${var.spark_master_host}" 32 | hdfs_block_size = "${var.hdfs_block_size}" 33 | } 34 | } 35 | 36 | resource "openstack_compute_instance_v2" "instance" { 37 | name="${var.name_prefix}-worker-${format("%03d", count.index)}" 38 | image_name = "${var.image_name}" 39 | flavor_name = "${var.flavor_name}" 40 | user_data = "${file("${path.module}/bootstrap.sh")}" 41 | key_pair = "${var.keypair_name}" 42 | user_data = "${template_file.bootstrap.rendered}" 43 | count = "${var.count}" 44 | volume = { 45 | volume_id = "${element(openstack_blockstorage_volume_v1.blockstorage.*.id, count.index)}" 46 | device = "${var.volume_device}" 47 | } 48 | network { 49 | name = "${var.network_name}" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /terraform/master/main.tf: -------------------------------------------------------------------------------- 1 | variable dc_name {} 2 | variable name_prefix {} 3 | variable floating_ip_pool {} 4 | variable image_name {} 5 | variable flavor_name {} 6 | variable keypair_name {} 7 | variable volume_size {} 8 | variable volume_device { default = "/dev/vdb" } 9 | variable ansible_opt { default = "" } 10 | variable ansible_tags { default = "master" } 11 | variable spark_rpc {} 12 | variable hdfs_block_size {} 13 | variable network_name {} 14 | variable zeppelinhub_api_token {} 15 | 16 | resource "openstack_blockstorage_volume_v1" "blockstorage" { 17 | name = "${var.name_prefix}-master-volume" 18 | size = "${var.volume_size}" 19 | } 20 | 21 | resource "openstack_compute_floatingip_v2" "master_floating_ip" { 22 | pool = "${var.floating_ip_pool}" 23 | } 24 | 25 | resource "template_file" "bootstrap" { 26 | template = "${file("${path.module}/bootstrap.sh")}" 27 | vars { 28 | dc_name = "${var.dc_name}" 29 | ansible_opt = "${var.ansible_opt}" 30 | ansible_tags = "${var.ansible_tags}" 31 | spark_rpc = "${var.spark_rpc}" 32 | hdfs_block_size = "${var.hdfs_block_size}" 33 | zeppelinhub_api_token = "${var.zeppelinhub_api_token}" 34 | } 35 | } 36 | 37 | resource "openstack_compute_instance_v2" "instance" { 38 | name="${var.name_prefix}-master" 39 | image_name = "${var.image_name}" 40 | flavor_name = "${var.flavor_name}" 41 | floating_ip = "${openstack_compute_floatingip_v2.master_floating_ip.address}" 42 | user_data = "${template_file.bootstrap.rendered}" 43 | key_pair = "${var.keypair_name}" 44 | volume = { 45 | volume_id = "${openstack_blockstorage_volume_v1.blockstorage.0.id}" 46 | device = "${var.volume_device}" 47 | } 48 | network { 49 | name = "${var.network_name}" 50 | } 51 | } 52 | 53 | output "ip_address" { 54 | value = "${openstack_compute_instance_v2.instance.0.network.0.fixed_ip_v4}" 55 | } 56 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | variable dc_name { default = "dc1" } 2 | variable keypair_name { } 3 | variable cluster_prefix { } 4 | variable floating_ip_pool { } 5 | variable SparkNow_image_name { } 6 | variable master_flavor_name { } 7 | variable worker_flavor_name { } 8 | variable worker_count { } 9 | variable master_volume_size { } 10 | variable worker_volume_size { } 11 | variable hdfs_block_size { default = "128M" } 12 | variable spark_rpc { default = "netty" } 13 | variable network_name { } 14 | variable ansible_opt { default = "" } 15 | variable zeppelinhub_api_token { default = "" } 16 | 17 | module "master_instance" { 18 | source = "./master" 19 | name_prefix = "${var.cluster_prefix}" 20 | floating_ip_pool = "${var.floating_ip_pool}" 21 | image_name = "${var.SparkNow_image_name}" 22 | flavor_name = "${var.master_flavor_name}" 23 | keypair_name = "${var.keypair_name}" 24 | volume_size = "${var.master_volume_size}" 25 | dc_name = "${var.dc_name}" 26 | hdfs_block_size = "${var.hdfs_block_size}" 27 | spark_rpc = "${var.spark_rpc}" 28 | network_name = "${var.network_name}" 29 | ansible_opt = "${var.ansible_opt}" 30 | zeppelinhub_api_token = "${var.zeppelinhub_api_token}" 31 | } 32 | 33 | module "worker_instances" { 34 | source = "./worker" 35 | name_prefix = "${var.cluster_prefix}" 36 | image_name = "${var.SparkNow_image_name}" 37 | flavor_name = "${var.worker_flavor_name}" 38 | keypair_name = "${var.keypair_name}" 39 | master_ip = "${module.master_instance.ip_address}" 40 | count = "${var.worker_count}" 41 | volume_size = "${var.worker_volume_size}" 42 | dc_name = "${var.dc_name}" 43 | spark_master_host = "${lower(var.cluster_prefix)}-master.node.${var.dc_name}.consul" 44 | hdfs_block_size = "${var.hdfs_block_size}" 45 | spark_rpc = "${var.spark_rpc}" 46 | network_name = "${var.network_name}" 47 | ansible_opt = "${var.ansible_opt}" 48 | } 49 | -------------------------------------------------------------------------------- /packer/provisioners/install-hdfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | echo "Creating Hadoop users..." 7 | sudo addgroup hadoop 8 | sudo adduser --disabled-password --gecos "" --ingroup hadoop hduser 9 | 10 | echo "Disabling IPv6 (not supported by Hadoop)..." 11 | sudo sh -c 'echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf' 12 | sudo sh -c 'echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf' 13 | sudo sh -c 'echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf' 14 | 15 | echo "Fetching Hadoop..." 16 | HADOOP_DOWNLOAD_URL=http://www-us.apache.org/dist/hadoop/common/hadoop-2.7.3/hadoop-2.7.3.tar.gz 17 | HADOOP_TGZ=${HADOOP_DOWNLOAD_URL##*/} 18 | HADOOP_PACKAGE_NAME=${HADOOP_TGZ%.tar.gz} 19 | wget -q $HADOOP_DOWNLOAD_URL -O /tmp/$HADOOP_TGZ 20 | 21 | echo "Installing $HADOOP_PACKAGE_NAME..." 22 | 23 | sudo mkdir /opt/hadoop/ 24 | sudo tar xzf /tmp/$HADOOP_TGZ -C /opt/hadoop/ 25 | sudo ln -s /opt/hadoop/$HADOOP_PACKAGE_NAME /opt/hadoop/default 26 | sudo chown hduser:hadoop -R /opt/hadoop/ 27 | 28 | # Set environment 29 | echo 'export HADOOP_HOME=/opt/hadoop/default' >> ~/.bashrc 30 | echo 'export PATH=$PATH:$HADOOP_HOME/bin' >> ~/.bashrc 31 | echo 'export PATH=$PATH:$HADOOP_HOME/sbin' >> ~/.bashrc 32 | echo 'export HADOOP_MAPRED_HOME=$HADOOP_HOME' >> ~/.bashrc 33 | echo 'export HADOOP_COMMON_HOME=$HADOOP_HOME' >> ~/.bashrc 34 | echo 'export HADOOP_HDFS_HOME=$HADOOP_HOME' >> ~/.bashrc 35 | echo 'export YARN_HOME=$HADOOP_HOME' >> ~/.bashrc 36 | echo 'export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native' >> ~/.bashrc 37 | echo 'export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib"' >> ~/.bashrc 38 | sudo sh -c 'echo "JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64" >> /opt/hadoop/default/etc/hadoop/hadoop-env.sh' 39 | 40 | # Move configuration to the correct location 41 | sudo mv /tmp/core-site.xml /opt/hadoop/default/etc/hadoop 42 | sudo mv /tmp/hdfs-site.xml /opt/hadoop/default/etc/hadoop 43 | 44 | echo "$HADOOP_PACKAGE_NAME installation complete." 45 | -------------------------------------------------------------------------------- /packer/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": 3 | { 4 | "image_name": "{{env `PACKER_IMAGE_NAME`}}", 5 | "source_image_name": "{{env `PACKER_SOURCE_IMAGE_NAME`}}", 6 | "network": "{{env `PACKER_NETWORK`}}", 7 | "flavor": "{{env `PACKER_FLAVOR`}}", 8 | "floating_ip_pool": "{{env `PACKER_FLOATING_IP_POOL`}}", 9 | "spark_download_url": "{{env `SPARK_DOWNLOAD_URL`}}" 10 | }, 11 | "builders": [ 12 | { 13 | "name": "all", 14 | "type": "openstack", 15 | "image_name": "{{user `image_name`}}", 16 | "source_image_name": "{{user `source_image_name`}}", 17 | "flavor": "{{user `flavor`}}", 18 | "networks": ["{{user `network`}}"], 19 | "floating_ip_pool": "{{user `floating_ip_pool`}}", 20 | "ssh_username": "ubuntu" 21 | } 22 | ], 23 | "provisioners": [ 24 | { 25 | "type": "shell", 26 | "script": "{{pwd}}/packer/provisioners/common.sh" 27 | }, 28 | { 29 | "type": "shell", 30 | "script": "{{pwd}}/packer/provisioners/install-spark.sh", 31 | "environment_vars": ["SPARK_DOWNLOAD_URL={{user `spark_download_url`}}"] 32 | }, 33 | { 34 | "type": "shell", 35 | "script": "{{pwd}}/packer/provisioners/install-docker.sh" 36 | }, 37 | { 38 | "type": "shell", 39 | "script": "{{pwd}}/packer/provisioners/install-jupyter.sh" 40 | }, 41 | { 42 | "type": "shell", 43 | "script": "{{pwd}}/packer/provisioners/install-zeppelin.sh" 44 | }, 45 | { 46 | "type": "file", 47 | "source": "{{pwd}}/packer/provisioners/core-site.xml", 48 | "destination": "/tmp/core-site.xml" 49 | }, 50 | { 51 | "type": "file", 52 | "source": "{{pwd}}/packer/provisioners/hdfs-site.xml", 53 | "destination": "/tmp/hdfs-site.xml" 54 | }, 55 | { 56 | "type": "shell", 57 | "script": "{{pwd}}/packer/provisioners/install-hdfs.sh" 58 | }, 59 | { 60 | "type": "shell", 61 | "script": "{{pwd}}/packer/provisioners/install-consul.sh" 62 | }, 63 | { 64 | "type": "file", 65 | "source": "{{pwd}}/ansible", 66 | "destination": "/tmp/playbooks" 67 | }, 68 | { 69 | "type": "shell", 70 | "script": "{{pwd}}/packer/provisioners/install-ansible.sh" 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SparkNow 2 | Using SparkNow you can rapidly deploy, scale and tear down your Spark clusters on OpenStack. Deploying a SparkNow cluster you will get: 3 | 4 | - A [Spark](http://spark.apache.org/) cluster up and running 5 | - A [HDFS](https://hadoop.apache.org/) cluster to store your data 6 | - A [Jupyter](https://github.com/jupyter/docker-stacks/tree/master/all-spark-notebook) notebook for interactive Spark tasks 7 | - A [Apache Zeppelin](https://zeppelin.apache.org/) notebook for interactive Spark tasks 8 | 9 | ## Table of contents 10 | - [Getting started](#getting-started) 11 | - [Get SparkNow](#get-sparknow) 12 | - [Install Packer and Terraform](#install-packer-and-terraform) 13 | - [Build SparkNow](#build-sparknow) 14 | - [Deploy a Spark cluster](#deploy-a-spark-cluster) 15 | - [Access Spark UI and Jupyter](#access-spark-ui-and-jupyter) 16 | - [Access HDFS](#access-hdfs) 17 | - [Scale the cluster](#scale-the-cluster) 18 | - [Destroy the cluster](#destroy-the-cluster) 19 | 20 | ## Getting started 21 | 22 | ### Install Packer and Terraform 23 | SparkNow uses Packer (https://www.packer.io/) and Terraform (https://www.terraform.io/), to build 24 | its OpenStack image and to provision the cluster. Please install both of them on your local machine, 25 | following the instruction on their websites. 26 | 27 | ### Get SparkNow 28 | To get SparkNow just clone this repository. 29 | 30 | ```bash 31 | git clone https://github.com/mcapuccini/SparkNow.git 32 | ``` 33 | 34 | ### Build SparkNow 35 | To build SparkNow on your OpenStack tenancy, first export the following environment variables on your local 36 | machine. 37 | 38 | ```bash 39 | export SPARK_DOWNLOAD_URL="http://d3kbcqa49mib13.cloudfront.net/spark-2.1.0-bin-hadoop2.7.tgz" 40 | # you can change the download URL if you need another Spark version, everything should work 41 | # as long as the binary is compatible with Hadoop 2.7 42 | 43 | export PACKER_IMAGE_NAME="SparkNow_spark-2.1.0-hadoop2.7" 44 | export PACKER_SOURCE_IMAGE_NAME="Ubuntu 14.04" #this may be different in your OpenStack tenancy 45 | export PACKER_NETWORK="" # your OpenStack tenancy private network id 46 | export PACKER_FLAVOR="" # the instance flavor that you want to use to build SparkNow 47 | export PACKER_AVAILABILITY_ZONE="" # an availability zone name in your OpenStack tenancy 48 | export PACKER_FLOATING_IP_POOL="" # a floating IP pool in your OpenStack tenancy 49 | ``` 50 | 51 | Then, access your OpenStack tenancy through the web interface, download the OpenStack RC file 52 | (Compute > Access & Security > API Access & Security > Download OpenStack RC FILE) and source it. 53 | 54 | ```bash 55 | source someproject-openrc.sh # you will be asked to type your password 56 | ``` 57 | 58 | Finally, locate in the SparkNow directory, and run Packer to build the SparkNow image. 59 | 60 | ```bash 61 | cd SparkNow/ 62 | packer build packer/build.json # you will be asked to type your password 63 | ``` 64 | 65 | If everything goes well, you will see the new image in the OpenStack web interface (Compute > Images). 66 | 67 | ### Deploy a Spark cluster 68 | First create a `conf.tfvars` file, specifying some properties for the Spark cluster that you aim to deploy. 69 | 70 | **conf.tfvars** 71 | 72 | ``` 73 | keypair_name = "your-keypair" 74 | cluster_prefix = "SparkNow" 75 | floating_ip_pool = "" 76 | network_name = "" 77 | SparkNow_image_name = "SparkNow_spark-2.1.0-hadoop2.7" 78 | master_flavor_name = "" 79 | worker_flavor_name = "" 80 | worker_count = "3" 81 | worker_volume_size = "20" 82 | master_volume_size = "10" 83 | ``` 84 | 85 | > 86 | - *keypair_name*: name of a key pair that you previously created, using the OpenStack web interface 87 | (Compute > Access & Security > Key Pairs). 88 | - *cluster_prefix*: prefix for the resources that will be created in your OpenStack tenancy 89 | - *floating_ip_pool*: a floating IP pool in your OpenStack tenancy 90 | - *network_name*: an existing private network name (where the instances will be attached) 91 | - *SparkNow_image_name*: the name of the SparkNow image that you built in the previous step 92 | - *master_flavor_name*: the Spark master instance flavor 93 | - *worker_flavor_name*: the Spark worker instance flavor 94 | - *worker_count*: number of Spark workers to deploy 95 | - *worker_volume_size*: the size of the worker instance volume in Gb 96 | - *master_volume_size*: the size of the master instance volume in Gb 97 | 98 | Run Terraform to deploy a Spark cluster (assuming you already sourced the OpenStack RC file). 99 | 100 | ```bash 101 | cd SparkNow/terraform 102 | terraform get # download terraform modules (required only the first time you deploy) 103 | terraform apply -var-file=conf.tfvars # deploy the cluster 104 | ``` 105 | 106 | If everity goes well, something like the following will be printed: 107 | 108 | ```bash 109 | Apply complete! Resources: 10 added, 0 changed, 0 destroyed. 110 | ``` 111 | 112 | ## Access Spark UI and Jupyter 113 | The best way to access the UIs is through ssh port forwarding. We discourage to open the ports in the security group. 114 | 115 | First, figure out the Spark Master floating IP address, running the following command. 116 | 117 | ```bash 118 | # assuming you are located into SparkNow/terraform 119 | terraform show | grep floating_ip 120 | ``` 121 | 122 | Then forward the UIs ports using ssh 123 | ```bash 124 | ssh -N -f -L localhost:8080:localhost:8080 ubuntu@ 125 | ssh -N -f -L localhost:4040:localhost:4040 ubuntu@ 126 | ssh -N -f -L localhost:8888:localhost:8888 ubuntu@ 127 | ssh -N -f -L localhost:9999:localhost:9999 ubuntu@ 128 | ssh -N -f -L localhost:50070:localhost:50070 ubuntu@ 129 | ``` 130 | 131 | If everything went well, you should be able to access the UIs from your browser at the following addresses. 132 | 133 | - Spark Master UI: [http://localhost:8080](http://localhost:8080) 134 | - Spark Driver UI, of the currently running application: [http://localhost:4040](http://localhost:4040) 135 | - Jupyter: [http://localhost:8888](http://localhost:8888) 136 | - Zeppelin: [http://localhost:9999](http://localhost:9999) 137 | - HDFS: [http://localhost:50070](http://localhost:50070) 138 | 139 | ## Access HDFS 140 | In a SparkNow cluster the HDFS namenode is reachable at `hdfs://-master.node.consul:9000`. 141 | 142 | To copy data in HDFS, you can ssh into the SparkNow master node, or ssh forward port 9000, and use the [Hadoop 143 | CLI](https://hadoop.apache.org/docs/r2.7.3/hadoop-project-dist/hadoop-common/CommandsManual.html). 144 | 145 | Finally, there are some preconfigured directories in a SparkNow HDFS cluster: 146 | 147 | - */ubuntu* writable by the ubuntu user 148 | - */jupyter* writable by the jovyan user (you can write here when running interactive Spark applications via Jupyter) 149 | - */root* writeble by the root user (can be used when running Zeppelin notes) 150 | 151 | ## Scale the cluster 152 | To scale the number of workers in your cluster, open the `conf.tfvars` file, and change the *worker_count* property. 153 | Then, apply the changes with Terraform. 154 | 155 | ```bash 156 | # assuming you are located into SparkNow/terraform 157 | terraform apply -var-file=conf.tfvars 158 | ``` 159 | 160 | Terraform will apply only the delta, without tearing down and recreate the whole cluster. 161 | 162 | ## Destroy the cluster 163 | To destroy the cluster and release all of the resources, you can run the following command. 164 | 165 | ```bash 166 | # assuming you are located into SparkNow/terraform 167 | terraform destroy -var-file=conf.tfvars 168 | ``` 169 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------