diff --git a/docs/source/batch.rst b/docs/source/batch.rst index c7aa7a00..1cda3132 100644 --- a/docs/source/batch.rst +++ b/docs/source/batch.rst @@ -4,14 +4,14 @@ AWS Batch Integration ======================= AWS Batch allows to efficiently execute batch computing jobs on AWS by dynamically provisioning the required underlying EC2 instances on which Docker-based jobs are executed. -SCAR allows to transparently integrate the execution of the jobs through `AWS Batch `_. +SCAR allows to transparently integrate the execution of the jobs through `AWS Batch `_. Three execution modes are now available in SCAR: * `lambda`: This is the default execution mode. All executions will be run on AWS Lambda. * `lambda-batch`: Executions will be run on AWS Lambda. If the default timeout is reached, then the execution is automatically delegated to AWS Batch. * `batch`: Executions will be automatically diverted to AWS Batch. -This way, you can use AWS Lambda as a highly-scalable cache for burts of short computational jobs while longer executions can be automatically delegated to AWS Batch. +This way, you can use AWS Lambda as a highly-scalable cache for burts of short computational jobs while longer executions can be automatically delegated to AWS Batch. The very same `programming model `_ is maintained regardless of the service employed to perform the computation. Set up your configuration file @@ -47,12 +47,12 @@ The variables responsible for batch configuration are:: }, "service_role": "arn:aws:iam::{account_id}:role/service-role/AWSBatchServiceRole" } - -Since AWS Batch deploys Amazon EC2 instances, the REQUIRED variables are: + +Since AWS Batch deploys Amazon EC2 instances, the REQUIRED variables are: * `security_group_ids`: The EC2 security group that is associated with the instances launched in the compute environment. This allows to define the inbound and outbound network rules in order to allow or disallow TCP/UDP traffic generated from (or received by) the EC2 instance. You can choose the default VPC security group. * `subnets`: The VPC subnet(s) identifier(s) on which the EC2 instances will be deployed. This allows to use multiple Availability Zones for enhanced fault-tolerance. -The remaining variables have default values that should be enough to manage standard batch jobs. +The remaining variables have default values that should be enough to manage standard batch jobs. The default `fdl file `_ explains briefly the remaining Batch variables and how are they used. Additional info about the variables and the different values that can be assigned can be found in the `AWS API Documentation `_. @@ -60,7 +60,7 @@ Additional info about the variables and the different values that can be assigne Set up your Batch IAM role -------------------------- -The default IAM role used in the creation of the EC2 for the Batch Compute Environment is **arn:aws:iam::$ACCOUNT_ID:instance-profile/**ecsInstanceRole****. Thus, if you want to provide S3 access to your Batch jobs you have to specify the corresponding policies in the aforementioned role. +The default IAM role used in the creation of the EC2 for the Batch Compute Environment is **arn:aws:iam::$ACCOUNT_ID:instance-profile/**ecsInstanceRole****. Thus, if you want to provide S3 access to your Batch jobs you have to specify the corresponding policies in the aforementioned role. If you have a role aleredy configured, you can set it in the configuration file by changin the variable `batch.compute_resources.instance_role`. @@ -105,10 +105,10 @@ And trigger the execution of the function by uploading a file to be processed to SCAR automatically creates the compute environment in AWS Batch and submits a job to be executed. Input and output data files are transparently managed as well according to the programming model. -The CloudWatch logs will reveal the execution of the Lambda function as well as the execution of the AWS Batch job. -Notice that whenever the execution of the AWS Batch job has finished, the EC2 instances will be eventually terminated. +The CloudWatch logs will reveal the execution of the Lambda function as well as the execution of the AWS Batch job. +Notice that whenever the execution of the AWS Batch job has finished, the EC2 instances will be eventually terminated. Also, the number of EC2 instances will increase and shrink to handle the incoming number of jobs. - + Combine AWS Lambda and AWS Batch executions ------------------------------------------- As explained in the section :doc:`/prog_model`, if you define an output bucket as the input bucket of another function, a workflow can be created. @@ -127,3 +127,13 @@ To create the AWS Batch job, the Lambda function defines a Job with the payload The payload limit can be avoided by redefining the script used and passing the large payload files using other service (e.g S3 or some bash command like 'wget' or 'curl' to download the information in execution time). As we didi with the plant classification example, where a `bootstrap script `_ was used to download the `executed script `_. Also, AWS Batch does not allow to override the container entrypoint so containers with an entrypoint defined can not execute an user script. + +Multinode parallel jobs +----------------------- +You can execute multinode parallel jobs in batch by enabling this mode either in the scar.cfg file or in the configuration file for the job (functions->aws->batch->multi_node_parallel->enable). +You can also set the number of nodes and the index of the main node. +Please take into account that the index of the main node starts from 0 up to the number of nodes -1. + +We included an `example `_ of MPI job that can be executed as multinode parallel job, showing a hello world from each CPU/node available for execution. +Both work in Amazon Lambda and Batch single node, you can use the included configuration files as a starting point. +For more details, please check the README.md that comes with the example. diff --git a/examples/mpi/Dockerfile b/examples/mpi/Dockerfile new file mode 100644 index 00000000..4691667d --- /dev/null +++ b/examples/mpi/Dockerfile @@ -0,0 +1,48 @@ +FROM debian:stretch-slim + +ARG ADD_BASE_DIR=examples/mpi +ARG BUILD_PACKAGES=' wget git make gcc g++ iproute2 cmake build-essential gfortran curl ' + +ENV DEBIAN_FRONTEND=noninteractive +## Set to either lambda or batch +ENV EXEC_TYPE=lambda + +ENV APP_PARAMS="" +ENV GIT_REPO=https://github.com/mpitutorial/mpitutorial +ENV GIT_REPO_REL_PATH_SRC=mpitutorial/tutorials/mpi-hello-world/code +ENV GIT_REPO_REL_PATH_EXEC=mpitutorial/tutorials/mpi-hello-world/code/mpi_hello_world +ENV APP_BIN=/opt/$GIT_REPO_REL_PATH_EXEC +ENV SSH_PRIV_FILE_KEY=ssh_host_rsa_key +ENV SSH_PUB_FILE_KEY=ssh_host_rsa_key.pub + +ENV LANG en_US.UTF-8 + +ADD ${ADD_BASE_DIR}/run.sh /opt/ + +RUN apt-get update \ + && apt-get install -q -o=Dpkg::Use-Pty=0 -y $BUILD_PACKAGES perl locales \ + && locale-gen en_US.UTF-8 \ + && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ + && dpkg-reconfigure --frontend=noninteractive locales \ + && update-locale LANG=en_US.UTF-8 \ + && wget -q --no-check-certificate -qO- https://download.open-mpi.org/release/open-mpi/v1.4/openmpi-1.4.3.tar.bz2 | tar xvfj - -C /tmp/ \ + && cd /tmp/openmpi-1.4.3/ \ + && ./configure --disable-pty-support --disable-doc \ + && make -j`nproc` \ + && make install \ + && ldconfig \ + && cd /opt/ \ + && git clone $GIT_REPO \ + && cd /opt/$GIT_REPO_REL_PATH_SRC \ + && make \ + && apt-get remove --purge -y $BUILD_PACKAGES gnupg* gnupg-agent* \ + && apt-get autoremove --purge -y \ + && rm -rf /tmp/* /var/lib/apt/lists/* \ + && ulimit -n 1024 \ + && chmod 755 /opt/$GIT_REPO_REL_PATH_EXEC \ + && chmod 755 /opt/run.sh \ + && echo $(date) > /build_date \ + && echo "Build date: $(cat /build_date)" + + +CMD /opt/run.sh diff --git a/examples/mpi/LICENSE b/examples/mpi/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/examples/mpi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/examples/mpi/README.md b/examples/mpi/README.md new file mode 100644 index 00000000..c8c451cd --- /dev/null +++ b/examples/mpi/README.md @@ -0,0 +1,69 @@ +# MPI with SCAR + + +Running a distributed MPI app in a Docker container on Lambda and Batch + +There are two different modes of running for this example: lambda through the __lambda.yaml__ and batch through __batch.yaml__. +This example uses the S3 bucket __scar-mpi__ for the input (the execution trigger) and the output of the job execution. +It is created automatically once a job is initialized, along with the input and output folders. + +The steps to run the example are: + +* Clone the repository (__/tmp__ for our example) + +`git clone https://github.com/grycap/scar /tmp` + +* Create a docker ignore if you plan to run Amazon Lambda or Batch with an uploaded image to DockerHub. The contents of the ignore are listed in this README. + +* [Lambda/Batch when upload to DockerHub] Build the the Docker image locally. **ADD_BASE_DIR** should be set as the relative path of the Github repo where the whole examples is found. Docker uses this to access additional files that are needed to be added inside the image (like __run.sh__). + +`docker build --build-arg ADD_BASE_DIR=scar/examples/mpi --label scar-mpi -t scar-mpi -f /tmp/scar/examples/mpi/Dockerfile /tmp` + +* [Lambda/Batch when upload local image to DockerHub] Dump the Docker image and upload it to DockerHub in case of Amazon Batch with container built locally. + +`sudo docker save scar-mpi > /tmp/scar-mpi.img` + +* Prepare __run_helper.yaml__. Follow the instructions inside the file. + +* [Batch] Adjust the location of __run_batch.sh__ in __batch.yaml__ + +* [Batch] Since AWS batch has to get the image from DockerHub, set the Docker repo/image in __batch.yaml__. You can create and automated build based on a Github repo by setting the **Build Context** as __/__ and the **Dockerfile location** as __examples/mpi/Dockerfile__ when asked by DockerHub. + +* Init the function using scar + +`scar init -f .yaml` + +* [Batch] Generate private/public keys used by ssh to communicate between nodes. The default names can be changed using the env variables SSH_PRIV_FILE_KEY and SSH_PUB_FILE_KEY + +* [Batch] Upload a tar.gz archive file with the public and private keys in the root of the archive to the root of the bucket __scar-mpi__ + +* [Batch] Be sure that you have access to the internet. You could create a private network for the node(s) and a NAT EC2 instance, both in the same security group. + +* [Batch] Upload the modified __run_helper.yaml__ file to the bucket __scar-mpi/input__ to start the execution + +* [Lambda] Run the function using SCAR + +`scar run -f lambda.yaml` + +* The results are uploaded automatically to __scar-mpi/output__ once the execution has been successfully finalized (__time.log__ for our example with the application log). The log file is also displayed in the console, so SCAR should show the result. + +## Git ignore examples + +Use these lines to create a __.dockerignore__ file in __/tmp__. +This is needed to avoid including unnecessary files during the image building + +``` +# Ignore everything +** + +# Allow files and directories +!/scar/examples/mpi/** + +# Ignore unnecessary files inside allowed directories +# This should go after the allowed directories +**/batch.yaml +**/lambda.yaml +**/run_helper.sh +**/README.md +**/LICENSE +``` diff --git a/examples/mpi/batch.yaml b/examples/mpi/batch.yaml new file mode 100644 index 00000000..0b124e95 --- /dev/null +++ b/examples/mpi/batch.yaml @@ -0,0 +1,24 @@ +functions: + aws: + - lambda: + name: scar-mpi + log_level: DEBUG + init_script: /tmp/run_batch.sh + execution_mode: batch + container: + image: + environment: + Variables: + EXEC_TYPE: batch + PYTHONIOENCODING: utf8 + input: + - storage_provider: s3 + path: scar-mpi/input + output: + - storage_provider: s3 + path: scar-mpi/output + batch: + multi_node_parallel: + enabled: true + number_nodes: 3 + main_node_index: 0 diff --git a/examples/mpi/lambda.yaml b/examples/mpi/lambda.yaml new file mode 100644 index 00000000..958633f8 --- /dev/null +++ b/examples/mpi/lambda.yaml @@ -0,0 +1,15 @@ +functions: + aws: + - lambda: + name: scar-mpi + run_script: /tmp/run_helper.sh + container: + image_file: /tmp/scar-mpi.img + environment: + Variables: + EXEC_TYPE: lambda + deployment: + bucket: scar-mpi + output: + - storage_provider: s3 + path: scar-mpi/output diff --git a/examples/mpi/run.sh b/examples/mpi/run.sh new file mode 100644 index 00000000..cbfdc36b --- /dev/null +++ b/examples/mpi/run.sh @@ -0,0 +1,238 @@ +#!/bin/bash +echo "Executing as AWS ${EXEC_TYPE}" +echo "Build date: $(cat /build_date)" +echo "Runing as: ${USER} home @ ${HOME}" +echo "Running with interpreter: $(readlink -f $(which sh))" +echo "Running MPI binary: ${APP_BIN}" + +log () { + echo "${BASENAME} - ${1}" +} + +# Standard function to print an error and exit with a failing return code +error_exit () { + log "${BASENAME} - ${1}" >&2 + log "${2:-1}" > $AWS_BATCH_EXIT_CODE_FILE + kill $(cat /tmp/supervisord.pid) +} + +usage () { + if [ "${#@}" -ne 0 ]; then + log "* ${*}" + log + fi + cat < $ip:$availablecores" + echo "$ip slots=$availablecores" >> $HOST_FILE_PATH + + lines=$(sort $HOST_FILE_PATH|uniq|wc -l) + i=0 + numCyclesWait=30 + while [ "$AWS_BATCH_JOB_NUM_NODES" -gt "$lines" ] && [ "$i" -lt "$numCyclesWait" ] + do + log "$lines out of $AWS_BATCH_JOB_NUM_NODES nodes joined, check again in 3 seconds" + sleep 3 + lines=$(sort $HOST_FILE_PATH|uniq|wc -l) + ((i=i+1)) + done + + if [ "$i" -eq "$numCyclesWait" ]; then + echo "children did not join" + exit 1 + fi + + # Make the temporary file executable and run it with any given arguments + log "All nodes successfully joined" + + # remove duplicates if there are any. + awk '!a[$0]++' $HOST_FILE_PATH > ${HOST_FILE_PATH}-deduped + cat $HOST_FILE_PATH-deduped + log "executing main MPIRUN workflow" + + { time mpirun --mca btl_tcp_if_include eth0 --debug-daemons -x PATH -x LD_LIBRARY_PATH --machinefile ${HOST_FILE_PATH}-deduped \ + ${APP_BIN} ${APP_PARAMS}; } 2>&1 | cat > ${TMP_OUTPUT_DIR}/time.log + sleep 2 + echo 'Exec output:' + cat ${TMP_OUTPUT_DIR}/time.log + + #if [ "${NODE_TYPE}" = 'main' ]; then + # env GZIP=-9 tar -czvf $SCRATCH_DIR/batch_output_${AWS_BATCH_JOB_ID}.tar.gz $SCRATCH_DIR/output/* + # aws s3 cp $SCRATCH_DIR/batch_output_${AWS_BATCH_JOB_ID}.tar.gz $S3_BUCKET/output/batch_output_${AWS_BATCH_JOB_ID}.tar.gz + #fi + + #log "done! goodbye, writing exit code to $AWS_BATCH_EXIT_CODE_FILE and shutting down my supervisord" + #echo "0" > $AWS_BATCH_EXIT_CODE_FILE + #kill $(cat /tmp/supervisord.pid) + #echo "#!/bin/bash" > ${S3_BATCH_MNT}/exec/docker_done + #echo "env GZIP=-9 tar -czvf /mnt/batch/output/result.tar.gz /mnt/batch/output/*" > ${S3_BATCH_MNT}/exec/docker_done + #echo "/usr/local/bin/aws s3 cp /mnt/batch/output/result.tar.gz s3://scar-architrave/output/result_$(date | tr ' ' _ ).tar.gz" >> ${S3_BATCH_MNT}/exec/docker_done + log "Signaling children to exit" + cat ${HOST_FILE_PATH}-deduped | awk -F_ '{print $1}' | xargs -I{} -n1 ssh {} "touch ${BATCH_SIGNAL_DIR}/master_done/done" + + log "Wait for children to finish their execution" + num_finished=$(ls ${BATCH_SIGNAL_DIR}/workers_done/|uniq|wc -l) + while [ "$AWS_BATCH_JOB_NUM_NODES" -gt "$((num_finished+1))" ] + do + log "$num_finished out of $AWS_BATCH_JOB_NUM_NODES nodes are done, check again in 1 second" + sleep 1 + num_finished=$(ls ${BATCH_SIGNAL_DIR}/workers_done/|uniq|wc -l) + done + + #while inotifywait ${S3_BATCH_MNT}/exec -e create; do { echo "EC2 host post-execution process completed, exiting container"; break; }; done + exit 0 +} + +# Fetch and run a script +report_to_master () { + # get own ip and num cpus + # + ip=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) + + if [ -x "$(command -v nvidia-smi)" ] ; then + NUM_GPUS=$(ls -l /dev/nvidia[0-9] | wc -l) + availablecores=$NUM_GPUS + else + availablecores=$(nproc) + fi + + log "I am a child node -> $ip:$availablecores, reporting to the master node -> ${AWS_BATCH_JOB_MAIN_NODE_PRIVATE_IPV4_ADDRESS}" +# echo "$ip slots=$availablecores" | ssh ${AWS_BATCH_JOB_MAIN_NODE_PRIVATE_IPV4_ADDRESS} "cat >> /$HOST_FILE_PATH" -vvv +# sleep 15 +# echo "$ip slots=$availablecores" | ssh ${AWS_BATCH_JOB_MAIN_NODE_PRIVATE_IPV4_ADDRESS} "cat >> /$HOST_FILE_PATH" -vvv + + until echo "$ip slots=$availablecores" | ssh ${AWS_BATCH_JOB_MAIN_NODE_PRIVATE_IPV4_ADDRESS} "cat >> /$HOST_FILE_PATH" + do + echo "Sleeping 5 seconds and trying again" + sleep 5 + done + + echo "Wait for master to finish" + while inotifywait ${BATCH_SIGNAL_DIR}/master_done -e create; do { echo "Child ${ip} has finished its execution, done! goodbye"; break; }; done + ssh ${AWS_BATCH_JOB_MAIN_NODE_PRIVATE_IPV4_ADDRESS} "touch ${BATCH_SIGNAL_DIR}/workers_done/${ip}" + exit 0 +} + +if [ "${EXEC_TYPE,,}" = 'lambda' ]; then + echo 'Run lambda' + export OMPI_MCA_plm_rsh_agent=/bin/false + { time mpirun -np 1 --debug-daemons ${APP_BIN} ${APP_PARAMS}; } 2>&1 | cat > $TMP_OUTPUT_DIR/time.log + cat $TMP_OUTPUT_DIR/time.log +elif [ "${EXEC_TYPE,,}" = 'batch' ]; then + echo 'Run batch' + + apt update + apt install -q -o=Dpkg::Use-Pty=0 -y inotify-tools iproute2 wget unzip openssh-server openssh-client + + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + export LC_CTYPE=en_US.UTF-8 + #locale-gen en_US.UTF-8 + #sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen + #dpkg-reconfigure --frontend=noninteractive locales + #update-locale LANG=en_US.UTF-8 + wget -nc -nv https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip + unzip -qq awscli-exe-linux-x86_64.zip + chmod +x aws/install + ./aws/install + /usr/local/bin/aws configure set default.s3.max_concurrent_requests 30 + /usr/local/bin/aws configure set default.s3.max_queue_size 10000 + /usr/local/bin/aws configure set default.s3.multipart_threshold 64MB + /usr/local/bin/aws configure set default.s3.multipart_chunksize 16MB + /usr/local/bin/aws configure set default.s3.max_bandwidth 4096MB/s + /usr/local/bin/aws configure set default.s3.addressing_style path + /usr/local/bin/aws s3 cp s3://scar-mpi-example/ssh.tar.gz /opt + cd /opt + tar -zvxf /opt/ssh.tar.gz + + echo "Configure ssh" + sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd + echo "export VISIBLE=now" >> /etc/profile + echo "${USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + mkdir -p ${HOME}/.ssh + touch ${HOME}/.ssh/sshd_config + #ssh-keygen -t rsa -f ${SSHDIR}/ssh_host_rsa_key -N '' + cat /opt/${SSH_PUB_FILE_KEY} > ${HOME}/.ssh/authorized_keys + cp /opt/${SSH_PRIV_FILE_KEY} ${HOME}/.ssh/id_rsa + echo " IdentityFile ${HOME}/.ssh/id_rsa" >> /etc/ssh/ssh_config + echo "Host *" >> /etc/ssh/ssh_config + echo " StrictHostKeyChecking no" >> /etc/ssh/ssh_config + echo "PermitRootLogin without-password" >> /etc/ssh/sshd_config + #sed -i -e 's/#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config + #cat /etc/ssh/sshd_config + chmod -R 600 ${HOME}/.ssh/* + chown -R ${USER}:${USER} ${HOME}/.ssh/ + # check if ssh agent is running or not, if not, run + eval `ssh-agent -s` + + chmod +x ${APP_BIN} + service ssh status + service ssh restart + service ssh status + ssh-add ${HOME}/.ssh/id_rsa + service ssh restart + echo "Running app" + BASENAME="${0##*/}" + HOST_FILE_PATH="/tmp/hostfile" + AWS_BATCH_EXIT_CODE_FILE="/tmp/batch-exit-code" + + BATCH_SIGNAL_DIR=/tmp/batch + if [ -d "${BATCH_SIGNAL_DIR}" ]; then rm -Rf ${BATCH_SIGNAL_DIR}; fi + mkdir -p ${BATCH_SIGNAL_DIR}/master_done + mkdir -p ${BATCH_SIGNAL_DIR}/workers_done + + sleep 2 + + # Set child by default switch to main if on main node container + NODE_TYPE="child" + if [ "${AWS_BATCH_JOB_MAIN_NODE_INDEX}" == "${AWS_BATCH_JOB_NODE_INDEX}" ]; then + log "Running synchronize as the main node" + NODE_TYPE="main" + fi + + + # Main - dispatch user request to appropriate function + log $NODE_TYPE + case $NODE_TYPE in + main) + wait_for_nodes "${@}" + ;; + + child) + report_to_master "${@}" + ;; + + *) log $NODE_TYPE + usage "Could not determine node type. Expected (main/child)" + ;; + esac + +else + echo "ERROR: unknown execution type '${EXEC_TYPE}'" + exit 1 # terminate and indicate error +fi diff --git a/examples/mpi/run_batch.sh b/examples/mpi/run_batch.sh new file mode 100644 index 00000000..7d22f90e --- /dev/null +++ b/examples/mpi/run_batch.sh @@ -0,0 +1 @@ +bash $INPUT_FILE_PATH diff --git a/examples/mpi/run_helper.sh b/examples/mpi/run_helper.sh new file mode 100644 index 00000000..953c2e16 --- /dev/null +++ b/examples/mpi/run_helper.sh @@ -0,0 +1,8 @@ +# Uncomment AWS_BATCH_JOB_NUM_NODES,AWS_BATCH_JOB_NODE_INDEX, and AWS_BATCH_JOB_MAIN_NODE_INDEX when running batch on single node +# export AWS_BATCH_JOB_NUM_NODES=1 +# export AWS_BATCH_JOB_NODE_INDEX=0 +# export AWS_BATCH_JOB_MAIN_NODE_INDEX=0 + +export APP_PARAMS='' +export APP_BIN='' +bash /opt/run.sh diff --git a/scar/parser/cfgfile.py b/scar/parser/cfgfile.py index ce47d5a3..25fb2858 100644 --- a/scar/parser/cfgfile.py +++ b/scar/parser/cfgfile.py @@ -131,6 +131,11 @@ "log_retention_policy_in_days": 30 }, "batch": { + "multi_node_parallel": { + "enabled": False, + "number_nodes": 10, + "main_node_index": 0 + }, "boto_profile": "default", "region": "us-east-1", "vcpus": 1, diff --git a/scar/providers/aws/batchfunction.py b/scar/providers/aws/batchfunction.py index f9caaf1c..9cfadedd 100644 --- a/scar/providers/aws/batchfunction.py +++ b/scar/providers/aws/batchfunction.py @@ -151,33 +151,41 @@ def _get_creations_job_queue_args(self): def _get_job_definition_args(self): job_def_args = { - 'jobDefinitionName': self.function_name, - 'type': 'container', - 'containerProperties': { - 'image': self.resources_info.get('lambda').get('container').get('image'), - 'memory': int(self.batch.get('memory')), - 'vcpus': int(self.batch.get('vcpus')), - 'command': [ - '/bin/sh', - '-c', - 'echo $EVENT | /opt/faas-supervisor/bin/supervisor' - ], - 'volumes': [ - { - 'host': { - 'sourcePath': '/opt/faas-supervisor/bin' - }, - 'name': 'supervisor-bin' - } - ], - 'environment': [{'name': key, 'value': value} for key, value in self.resources_info['batch']['environment']['Variables'].items()], - 'mountPoints': [ - { - 'containerPath': '/opt/faas-supervisor/bin', - 'sourceVolume': 'supervisor-bin' - } - ] - } + 'jobDefinitionName': self.function_name + } + if self.batch.get('multi_node_parallel').get('enabled'): + job_def_args['nodeProperties'] = self._get_node_properties_multi_node_args() + job_def_args['type'] = 'multinode' + else: + job_def_args['containerProperties'] = self._get_container_properties_single_node_args() + job_def_args['type'] = 'container' + return job_def_args + + def _get_container_properties_single_node_args(self): + job_def_args = { + 'image': self.resources_info.get('lambda').get('container').get('image'), + 'memory': int(self.batch.get('memory')), + 'vcpus': int(self.batch.get('vcpus')), + 'command': [ + '/bin/sh', + '-c', + 'echo $EVENT | /opt/faas-supervisor/bin/supervisor' + ], + 'volumes': [ + { + 'host': { + 'sourcePath': '/opt/faas-supervisor/bin' + }, + 'name': 'supervisor-bin' + } + ], + 'environment': [{'name': key, 'value': value} for key, value in self.resources_info['batch']['environment']['Variables'].items()], + 'mountPoints': [ + { + 'containerPath': '/opt/faas-supervisor/bin', + 'sourceVolume': 'supervisor-bin' + } + ] } if self.batch.get('enable_gpu'): job_def_args['containerProperties']['resourceRequirements'] = [ @@ -188,6 +196,20 @@ def _get_job_definition_args(self): ] return job_def_args + def _get_node_properties_multi_node_args(self): + targetNodes = self.batch.get('multi_node_parallel').get('number_nodes') - 1 + job_def_args = { + "numNodes": int(self.batch.get('multi_node_parallel').get('number_nodes')), + "mainNode": int(self.batch.get('multi_node_parallel').get('main_node_index')), + "nodeRangeProperties": [ + { + "targetNodes": "0:", #+ str(targetNodes), + "container": self._get_container_properties_single_node_args() + } + ]#[self._get_node_node_range_property_multi_node_args(target_nodes) for target_nodes in self.batch.get('multi_node_parallel').get('target_nodes')] + } + return job_def_args + def _get_state_and_status_of_compute_env(self): creation_args = self._get_describe_compute_env_args() response = self.client.describe_compute_environments(**creation_args) diff --git a/scar/providers/aws/functioncode.py b/scar/providers/aws/functioncode.py index 2979eca7..3541d454 100644 --- a/scar/providers/aws/functioncode.py +++ b/scar/providers/aws/functioncode.py @@ -25,7 +25,7 @@ def clean_function_config(function_cfg: Dict): # Rm full path from the init_script - if function_cfg.get('init_script', True): + if 'init_script' in function_cfg and function_cfg.get('init_script', True): function_cfg['init_script'] = ntpath.basename(function_cfg['init_script']) # Rm the config path function_cfg.pop('config_path', None) @@ -35,6 +35,11 @@ def create_function_config(resources_info): function_cfg = {'storage_providers': FileUtils.load_tmp_config_file().get('storage_providers', {})} function_cfg.update(resources_info.get('lambda')) clean_function_config(function_cfg) + # Add Batch specific info + if resources_info.get('lambda').get("execution_mode") == "batch": + function_cfg.update({"batch": { + "multi_node_parallel": resources_info.get('batch').get("multi_node_parallel") + }}) return function_cfg diff --git a/scar/version.py b/scar/version.py index 45f1fdec..924fde74 100644 --- a/scar/version.py +++ b/scar/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = '4.1.0' +__version__ = '4.2.0'