You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openserverless.apache.org by mi...@apache.org on 2026/06/30 17:59:09 UTC

(openserverless-build) 02/02: Vendor OpenWhisk sources

This is an automated email from the ASF dual-hosted git repository.

miki3421 pushed a commit to branch build-local
in repository https://gitbox.apache.org/repos/asf/openserverless-build.git

commit 1a78f1f6e4686793dd2af652dc67b746b7699acd
Author: Michele Sciabarra <mi...@nuvolaris.io>
AuthorDate: Tue Jun 30 13:06:19 2026 +0000

    Vendor OpenWhisk sources
---
 .gitmodules                                        |    3 -
 Taskfile.yml                                       |    6 +-
 openwhisk                                          |    1 -
 openwhisk/.asf.yaml                                |   52 +
 openwhisk/.gitattributes                           |   33 +
 openwhisk/.github/ISSUE_TEMPLATE.md                |   52 +
 openwhisk/.github/ISSUE_TEMPLATE/proposal.md       |    7 +
 openwhisk/.github/PULL_REQUEST_TEMPLATE.md         |   43 +
 openwhisk/.github/workflows/0-on-demand.yaml       |   92 +
 openwhisk/.github/workflows/1-unit.yaml            |   79 +
 openwhisk/.github/workflows/2-system.yaml          |   74 +
 openwhisk/.github/workflows/3-multi-runtime.yaml   |   75 +
 openwhisk/.github/workflows/4-standalone.yaml      |   74 +
 openwhisk/.github/workflows/5-scheduler.yaml       |   74 +
 openwhisk/.github/workflows/6-performance.yaml     |   88 +
 openwhisk/.github/workflows/README.md              |   56 +
 openwhisk/.gitignore                               |  104 +
 openwhisk/.pydevproject                            |    5 +
 openwhisk/.scalafmt.conf                           |   23 +
 openwhisk/.travis.yml                              |  106 +
 openwhisk/CHANGELOG.md                             |   91 +
 openwhisk/CODE_OF_CONDUCT.md                       |   22 +
 openwhisk/CONTRIBUTING.md                          |   71 +
 openwhisk/CREDITS.txt                              |   90 +
 openwhisk/Jenkinsfile                              |  144 +
 openwhisk/LICENSE.txt                              |  218 ++
 openwhisk/NOTICE.txt                               |    5 +
 openwhisk/README.md                                |  119 +
 openwhisk/Taskfile.yml                             |  106 +
 openwhisk/ansible/README.md                        |  540 ++++
 openwhisk/ansible/ansible.cfg                      |   12 +
 openwhisk/ansible/apigateway.yml                   |   24 +
 openwhisk/ansible/callbacks/logformatter.py        |   92 +
 openwhisk/ansible/controller.yml                   |   50 +
 openwhisk/ansible/couchdb.yml                      |   22 +
 openwhisk/ansible/downloadcli-github.yml           |   31 +
 openwhisk/ansible/downloadcli.yml                  |   22 +
 openwhisk/ansible/edge.yml                         |   26 +
 openwhisk/ansible/elasticsearch.yml                |   34 +
 .../environments/docker-machine/group_vars/all     |   50 +
 .../environments/docker-machine/hosts.j2.ini       |   52 +
 .../environments/jenkins/ansible_jenkins.cfg       |   13 +
 .../jenkins/group_vars/openwhisk-vm1-he-de         |   53 +
 .../jenkins/group_vars/openwhisk-vm2-he-de         |   54 +
 .../jenkins/group_vars/openwhisk-vm3-he-de         |   53 +
 .../jenkins/openwhisk-vm1-he-de.j2.ini             |   36 +
 .../jenkins/openwhisk-vm2-he-de.j2.ini             |   36 +
 .../jenkins/openwhisk-vm3-he-de.j2.ini             |   36 +
 .../ansible/environments/local/group_vars/all      |   50 +
 openwhisk/ansible/environments/local/hosts.j2.ini  |   50 +
 .../ansible/environments/vagrant/group_vars/all    |   50 +
 openwhisk/ansible/environments/vagrant/hosts       |   32 +
 openwhisk/ansible/etcd.yml                         |   22 +
 ...vations_design_document_for_activations_db.json |    9 +
 openwhisk/ansible/files/auth.guest                 |    1 +
 openwhisk/ansible/files/auth.whisk.system          |    1 +
 ...uth_design_document_for_subjects_db_v2.0.0.json |   10 +
 .../ansible/files/filter_design_document.json      |    6 +
 openwhisk/ansible/files/genssl.sh                  |  141 +
 ...Cleanup_design_document_for_activations_db.json |    9 +
 ...hrottlings_design_document_for_subjects_db.json |   10 +
 openwhisk/ansible/files/package-versions.ini       |    2 +
 openwhisk/ansible/files/runtimes-nodeonly.json     |   46 +
 openwhisk/ansible/files/runtimes.json              |  268 ++
 ...document_for_activations_db_filters_v2.1.0.json |   10 +
 ...document_for_activations_db_filters_v2.1.1.json |   10 +
 ..._design_document_for_activations_db_v2.1.0.json |   10 +
 ...sks_design_document_for_entities_db_v2.1.0.json |   26 +
 openwhisk/ansible/group_vars/all                   |  553 ++++
 openwhisk/ansible/initMongoDB.yml                  |   39 +
 openwhisk/ansible/initdb.yml                       |   23 +
 openwhisk/ansible/invoker.yml                      |   42 +
 openwhisk/ansible/kafka.yml                        |   26 +
 openwhisk/ansible/library/mongodb.py               |  283 ++
 openwhisk/ansible/logs.yml                         |   81 +
 openwhisk/ansible/mongodb.yml                      |   36 +
 openwhisk/ansible/openwhisk.yml                    |   39 +
 openwhisk/ansible/postdeploy.yml                   |   26 +
 openwhisk/ansible/prereq.yml                       |   24 +
 openwhisk/ansible/properties.yml                   |   23 +
 openwhisk/ansible/publish.yml                      |   24 +
 openwhisk/ansible/recreateDesignDocs.yml           |   23 +
 openwhisk/ansible/roles/apigateway/tasks/clean.yml |   24 +
 .../ansible/roles/apigateway/tasks/deploy.yml      |   46 +
 openwhisk/ansible/roles/apigateway/tasks/main.yml  |   26 +
 .../ansible/roles/cli-install/tasks/clean.yml      |   24 +
 .../ansible/roles/cli-install/tasks/deploy.yml     |   30 +
 openwhisk/ansible/roles/cli-install/tasks/main.yml |   26 +
 openwhisk/ansible/roles/cli/tasks/clean.yml        |   23 +
 openwhisk/ansible/roles/cli/tasks/deploy.yml       |   87 +
 openwhisk/ansible/roles/cli/tasks/main.yml         |   26 +
 openwhisk/ansible/roles/controller/tasks/clean.yml |   40 +
 .../ansible/roles/controller/tasks/deploy.yml      |  482 +++
 .../roles/controller/tasks/join_akka_cluster.yml   |   52 +
 openwhisk/ansible/roles/controller/tasks/lean.yml  |   54 +
 openwhisk/ansible/roles/controller/tasks/main.yml  |   26 +
 .../ansible/roles/controller/templates/config.j2   |    8 +
 openwhisk/ansible/roles/couchdb/tasks/clean.yml    |   24 +
 openwhisk/ansible/roles/couchdb/tasks/deploy.yml   |  186 ++
 openwhisk/ansible/roles/couchdb/tasks/main.yml     |   26 +
 .../ansible/roles/elasticsearch/tasks/clean.yml    |   39 +
 .../ansible/roles/elasticsearch/tasks/deploy.yml   |   94 +
 .../ansible/roles/elasticsearch/tasks/main.yml     |   26 +
 .../elasticsearch/templates/elasticsearch.yml.j2   |   23 +
 .../elasticsearch/templates/log4j2.properties.j2   |   10 +
 openwhisk/ansible/roles/etcd/tasks/clean.yml       |   25 +
 openwhisk/ansible/roles/etcd/tasks/deploy.yml      |   70 +
 openwhisk/ansible/roles/etcd/tasks/main.yml        |   26 +
 openwhisk/ansible/roles/invoker/tasks/clean.yml    |  112 +
 openwhisk/ansible/roles/invoker/tasks/deploy.yml   |  452 +++
 openwhisk/ansible/roles/invoker/tasks/main.yml     |   26 +
 .../ansible/roles/invoker/templates/config.j2      |    8 +
 openwhisk/ansible/roles/kafka/tasks/clean.yml      |   34 +
 openwhisk/ansible/roles/kafka/tasks/deploy.yml     |  110 +
 openwhisk/ansible/roles/kafka/tasks/main.yml       |   26 +
 openwhisk/ansible/roles/mongodb/tasks/clean.yml    |   24 +
 openwhisk/ansible/roles/mongodb/tasks/deploy.yml   |   38 +
 openwhisk/ansible/roles/mongodb/tasks/main.yml     |   27 +
 .../roles/nginx/files/openwhisk-server-key.pem     |   27 +
 openwhisk/ansible/roles/nginx/tasks/clean.yml      |   37 +
 openwhisk/ansible/roles/nginx/tasks/deploy.yml     |  104 +
 openwhisk/ansible/roles/nginx/tasks/main.yml       |   26 +
 .../ansible/roles/nginx/templates/nginx.conf.j2    |  165 +
 openwhisk/ansible/roles/prereq/tasks/clean.yml     |   36 +
 openwhisk/ansible/roles/prereq/tasks/deploy.yml    |   46 +
 openwhisk/ansible/roles/prereq/tasks/main.yml      |   26 +
 openwhisk/ansible/roles/redis/tasks/clean.yml      |   24 +
 openwhisk/ansible/roles/redis/tasks/deploy.yml     |   47 +
 openwhisk/ansible/roles/redis/tasks/main.yml       |   26 +
 .../roles/routemgmt/files/installRouteMgmt.sh      |  102 +
 .../roles/routemgmt/files/uninstallRouteMgmt.sh    |   97 +
 openwhisk/ansible/roles/routemgmt/tasks/clean.yml  |   23 +
 openwhisk/ansible/roles/routemgmt/tasks/deploy.yml |   22 +
 openwhisk/ansible/roles/routemgmt/tasks/main.yml   |   26 +
 openwhisk/ansible/roles/schedulers/tasks/clean.yml |   24 +
 .../ansible/roles/schedulers/tasks/deploy.yml      |  342 +++
 .../roles/schedulers/tasks/join_akka_cluster.yml   |   38 +
 openwhisk/ansible/roles/schedulers/tasks/main.yml  |   10 +
 .../ansible/roles/schedulers/templates/jmx.yml.j2  |   25 +
 openwhisk/ansible/roles/zookeeper/tasks/clean.yml  |   34 +
 openwhisk/ansible/roles/zookeeper/tasks/deploy.yml |   50 +
 openwhisk/ansible/roles/zookeeper/tasks/main.yml   |   26 +
 openwhisk/ansible/routemgmt.yml                    |   22 +
 openwhisk/ansible/scheduler.yml                    |   37 +
 openwhisk/ansible/setup.yml                        |  112 +
 openwhisk/ansible/tasks/db/checkDb.yml             |   34 +
 openwhisk/ansible/tasks/db/createUsers.yml         |   48 +
 openwhisk/ansible/tasks/db/grantPermissions.yml    |   77 +
 openwhisk/ansible/tasks/db/recreateDb.yml          |   50 +
 openwhisk/ansible/tasks/db/recreateDoc.yml         |   69 +
 openwhisk/ansible/tasks/docker_login.yml           |   26 +
 openwhisk/ansible/tasks/gen_erl_cookie.yml         |   49 +
 openwhisk/ansible/tasks/initdb.yml                 |   68 +
 .../ansible/tasks/installOpenwhiskCatalog.yml      |   64 +
 openwhisk/ansible/tasks/recreateViews.yml          |   37 +
 openwhisk/ansible/tasks/wipeDatabase.yml           |   53 +
 openwhisk/ansible/tasks/writeWhiskProperties.yml   |   41 +
 openwhisk/ansible/teardown.yml                     |   26 +
 openwhisk/ansible/templates/db_local.ini.j2        |   19 +
 openwhisk/ansible/templates/jmxremote.access.j2    |    1 +
 openwhisk/ansible/templates/jmxremote.password.j2  |    1 +
 openwhisk/ansible/templates/whisk.conf.j2          |   27 +
 openwhisk/ansible/templates/whisk.properties.j2    |   99 +
 openwhisk/ansible/wipe.yml                         |   25 +
 openwhisk/ansible/yamllint.yml                     |   24 +
 openwhisk/bin/wskadmin                             |    1 +
 openwhisk/bin/wskdev                               |    1 +
 openwhisk/build.gradle                             |  132 +
 openwhisk/common/scala/.dockerignore               |    4 +
 openwhisk/common/scala/Dockerfile                  |   42 +
 openwhisk/common/scala/Dockerfile-debian           |   37 +
 openwhisk/common/scala/build.gradle                |  114 +
 openwhisk/common/scala/copyJMXFiles.sh             |   28 +
 .../scala/src/main/resources/application.conf      |  612 ++++
 .../common/scala/src/main/resources/logback.xml    |   20 +
 .../common/scala/src/main/resources/logging.conf   |   22 +
 .../common/scala/src/main/resources/reference.conf |   89 +
 .../scala/src/main/resources/s3-reference.conf     |  115 +
 .../openwhisk/common/AverageRingBuffer.scala       |   66 +
 .../org/apache/openwhisk/common/CausedBy.scala     |   35 +
 .../scala/org/apache/openwhisk/common/Config.scala |  179 ++
 .../org/apache/openwhisk/common/ConfigMXBean.scala |   55 +
 .../apache/openwhisk/common/ConfigMapValue.scala   |   46 +
 .../org/apache/openwhisk/common/Counter.scala      |   47 +
 .../apache/openwhisk/common/ExecutorCloser.scala   |   44 +
 .../openwhisk/common/ForcibleSemaphore.scala       |  124 +
 .../scala/org/apache/openwhisk/common/Https.scala  |   95 +
 .../org/apache/openwhisk/common/Logging.scala      |  632 ++++
 .../org/apache/openwhisk/common/Message.scala      |   61 +
 .../apache/openwhisk/common/NestedSemaphore.scala  |  116 +
 .../org/apache/openwhisk/common/Prometheus.scala   |   52 +
 .../openwhisk/common/ResizableSemaphore.scala      |  115 +
 .../org/apache/openwhisk/common/RingBuffer.scala   |   32 +
 .../org/apache/openwhisk/common/Scheduler.scala    |  125 +
 .../apache/openwhisk/common/TransactionId.scala    |  308 ++
 .../org/apache/openwhisk/common/UserEvents.scala   |   38 +
 .../apache/openwhisk/common/WhiskInstants.scala    |   39 +
 .../org/apache/openwhisk/common/time/Clock.scala   |   28 +
 .../common/tracing/OpenTracingProvider.scala       |  209 ++
 .../connector/kafka/KafkaConsumerConnector.scala   |  210 ++
 .../connector/kafka/KafkaMessagingProvider.scala   |  145 +
 .../openwhisk/connector/kafka/KafkaMetrics.scala   |   75 +
 .../connector/kafka/KafkaProducerConnector.scala   |  127 +
 .../connector/kafka/KamonMetricsReporter.scala     |  141 +
 .../openwhisk/connector/lean/LeanConsumer.scala    |   49 +
 .../connector/lean/LeanMessagingProvider.scala     |   69 +
 .../openwhisk/connector/lean/LeanProducer.scala    |   58 +
 .../org/apache/openwhisk/core/FeatureFlags.scala   |   28 +
 .../scala/org/apache/openwhisk/core/WarmUp.scala   |   72 +
 .../org/apache/openwhisk/core/WhiskConfig.scala    |  326 ++
 .../scala/org/apache/openwhisk/core/ack/Ack.scala  |   55 +
 .../openwhisk/core/ack/HealthActionAck.scala       |   45 +
 .../openwhisk/core/ack/MessagingActiveAck.scala    |   71 +
 .../apache/openwhisk/core/connector/Message.scala  |  749 +++++
 .../openwhisk/core/connector/MessageConsumer.scala |  247 ++
 .../openwhisk/core/connector/MessageProducer.scala |   32 +
 .../core/connector/MessagingProvider.scala         |   46 +
 .../core/containerpool/AkkaContainerClient.scala   |  269 ++
 .../ApacheBlockingContainerClient.scala            |  267 ++
 .../openwhisk/core/containerpool/Container.scala   |  298 ++
 .../core/containerpool/ContainerClient.scala       |   30 +
 .../core/containerpool/ContainerFactory.scala      |  163 +
 .../logging/DockerToActivationFileLogStore.scala   |  187 ++
 .../logging/DockerToActivationLogStore.scala       |  197 ++
 .../logging/ElasticSearchLogStore.scala            |  131 +
 .../logging/ElasticSearchRestClient.scala          |  176 ++
 .../containerpool/logging/LogDriverLogStore.scala  |   65 +
 .../core/containerpool/logging/LogStore.scala      |  105 +
 .../containerpool/logging/SplunkLogStore.scala     |  208 ++
 .../core/database/ActivationFileStorage.scala      |  134 +
 .../openwhisk/core/database/ActivationStore.scala  |  233 ++
 .../core/database/ActivationStoreLevel.scala       |   29 +
 .../core/database/ArtifactActivationStore.scala    |  134 +
 .../openwhisk/core/database/ArtifactStore.scala    |  150 +
 .../core/database/ArtifactStoreExceptions.scala    |   44 +
 .../core/database/ArtifactStoreProvider.scala      |   47 +
 .../ArtifactWithFileStorageActivationStore.scala   |   74 +
 .../openwhisk/core/database/AttachmentStore.scala  |   70 +
 .../core/database/AttachmentSupport.scala          |  190 ++
 .../apache/openwhisk/core/database/Batcher.scala   |   95 +
 .../core/database/ConcurrentMapBackedCache.scala   |   78 +
 .../core/database/CouchDbRestClient.scala          |  174 ++
 .../openwhisk/core/database/CouchDbRestStore.scala |  571 ++++
 .../core/database/CouchDbStoreProvider.scala       |   81 +
 .../openwhisk/core/database/DocumentFactory.scala  |  222 ++
 .../openwhisk/core/database/DocumentHandler.scala  |  464 +++
 .../MultipleReadersSingleWriterCache.scala         |  464 +++
 .../core/database/RemoteCacheInvalidation.scala    |  106 +
 .../openwhisk/core/database/StoreUtils.scala       |  144 +
 .../database/azblob/AzureBlobAttachmentStore.scala |  326 ++
 .../cosmosdb/CollectionResourceUsage.scala         |   71 +
 .../database/cosmosdb/CosmosDBArtifactStore.scala  |  593 ++++
 .../cosmosdb/CosmosDBArtifactStoreProvider.scala   |  120 +
 .../core/database/cosmosdb/CosmosDBConfig.scala    |   96 +
 .../core/database/cosmosdb/CosmosDBSupport.scala   |  105 +
 .../core/database/cosmosdb/CosmosDBUtil.scala      |  115 +
 .../database/cosmosdb/CosmosDBViewMapper.scala     |  389 +++
 .../core/database/cosmosdb/IndexingPolicy.scala    |  115 +
 .../core/database/cosmosdb/ReferenceCounted.scala  |   50 +
 .../database/cosmosdb/RetryMetricsCollector.scala  |  138 +
 .../database/cosmosdb/RxObservableImplicits.scala  |   57 +
 .../ElasticSearchActivationStore.scala             |  472 +++
 .../core/database/memory/MemoryArtifactStore.scala |  347 +++
 .../database/memory/MemoryAttachmentStore.scala    |  144 +
 .../core/database/memory/MemoryViewMapper.scala    |  253 ++
 .../core/database/memory/NoopActivationStore.scala |   93 +
 .../database/mongodb/MongoDBArtifactStore.scala    |  659 ++++
 .../mongodb/MongoDBArtifactStoreProvider.scala     |   96 +
 .../database/mongodb/MongoDBAsyncStreamSink.scala  |  122 +
 .../mongodb/MongoDBAsyncStreamSource.scala         |  104 +
 .../core/database/mongodb/MongoDBViewMapper.scala  |  224 ++
 .../core/database/s3/CloudFrontSigner.scala        |   54 +
 .../core/database/s3/S3AttachmentStore.scala       |  252 ++
 .../openwhisk/core/entitlement/Privilege.scala     |   60 +
 .../core/entity/ActivationEntityLimit.scala        |   39 +
 .../openwhisk/core/entity/ActivationId.scala       |   96 +
 .../openwhisk/core/entity/ActivationLogs.scala     |   35 +
 .../openwhisk/core/entity/ActivationResult.scala   |  260 ++
 .../apache/openwhisk/core/entity/Annotations.scala |   29 +
 .../openwhisk/core/entity/ArgNormalizer.scala      |   47 +
 .../apache/openwhisk/core/entity/Attachments.scala |   98 +
 .../core/entity/BasicAuthenticationAuthKey.scala   |   65 +
 .../apache/openwhisk/core/entity/CacheKey.scala    |   55 +
 .../openwhisk/core/entity/ConcurrencyLimit.scala   |   91 +
 .../apache/openwhisk/core/entity/CreationId.scala  |   87 +
 .../org/apache/openwhisk/core/entity/DocInfo.scala |  192 ++
 .../openwhisk/core/entity/DocumentReader.scala     |   24 +
 .../apache/openwhisk/core/entity/EntityPath.scala  |  236 ++
 .../org/apache/openwhisk/core/entity/Exec.scala    |  479 +++
 .../openwhisk/core/entity/ExecManifest.scala       |  419 +++
 .../core/entity/FullyQualifiedEntityName.scala     |  138 +
 .../openwhisk/core/entity/GenericAuthKey.scala     |   48 +
 .../apache/openwhisk/core/entity/Identity.scala    |  157 +
 .../apache/openwhisk/core/entity/InstanceId.scala  |  196 ++
 .../org/apache/openwhisk/core/entity/Limits.scala  |   90 +
 .../apache/openwhisk/core/entity/LogLimit.scala    |   91 +
 .../apache/openwhisk/core/entity/MemoryLimit.scala |   87 +
 .../apache/openwhisk/core/entity/Parameter.scala   |  325 ++
 .../core/entity/ParameterEncryption.scala          |  163 +
 .../org/apache/openwhisk/core/entity/Secret.scala  |   75 +
 .../org/apache/openwhisk/core/entity/SemVer.scala  |  102 +
 .../org/apache/openwhisk/core/entity/Size.scala    |  218 ++
 .../org/apache/openwhisk/core/entity/Subject.scala |   71 +
 .../apache/openwhisk/core/entity/TimeLimit.scala   |   97 +
 .../org/apache/openwhisk/core/entity/UUID.scala    |   73 +
 .../apache/openwhisk/core/entity/WhiskAction.scala |  655 ++++
 .../openwhisk/core/entity/WhiskActivation.scala    |  217 ++
 .../apache/openwhisk/core/entity/WhiskAuth.scala   |   61 +
 .../apache/openwhisk/core/entity/WhiskEntity.scala |  226 ++
 .../openwhisk/core/entity/WhiskPackage.scala       |  299 ++
 .../apache/openwhisk/core/entity/WhiskRule.scala   |  250 ++
 .../apache/openwhisk/core/entity/WhiskStore.scala  |  237 ++
 .../openwhisk/core/entity/WhiskTrigger.scala       |  127 +
 .../apache/openwhisk/core/etcd/EtcdClient.scala    |  284 ++
 .../org/apache/openwhisk/core/etcd/EtcdUtils.scala |  250 ++
 .../apache/openwhisk/core/etcd/EtcdWorker.scala    |  166 +
 .../core/service/DataManagementService.scala       |  179 ++
 .../core/service/LeaseKeepAliveService.scala       |  180 ++
 .../openwhisk/core/service/WatcherService.scala    |  159 +
 .../openwhisk/core/yarn/YARNComponentActor.scala   |  105 +
 .../openwhisk/core/yarn/YARNContainerFactory.scala |  240 ++
 .../core/yarn/YARNContainerInfoActor.scala         |  120 +
 .../apache/openwhisk/core/yarn/YARNRESTUtil.scala  |  200 ++
 .../org/apache/openwhisk/core/yarn/YARNTask.scala  |   91 +
 .../apache/openwhisk/http/BasicHttpService.scala   |  201 ++
 .../apache/openwhisk/http/BasicRasService.scala    |   42 +
 .../org/apache/openwhisk/http/CorsSettings.scala   |   67 +
 .../org/apache/openwhisk/http/ErrorResponse.scala  |  296 ++
 .../openwhisk/http/LenientSprayJsonSupport.scala   |   29 +
 .../apache/openwhisk/http/PoolingRestClient.scala  |  149 +
 .../scala/org/apache/openwhisk/spi/SpiLoader.scala |   51 +
 .../org/apache/openwhisk/utils/Exceptions.scala    |   54 +
 .../openwhisk/utils/ExecutionContextFactory.scala  |  100 +
 .../org/apache/openwhisk/utils/JsHelpers.scala     |   43 +
 .../scala/org/apache/openwhisk/utils/Retry.scala   |   49 +
 .../org/apache/openwhisk/utils/TimeHelpers.scala   |   28 +
 openwhisk/common/scala/transformEnvironment.sh     |   86 +
 openwhisk/core/controller/.dockerignore            |    4 +
 openwhisk/core/controller/Dockerfile               |   69 +
 openwhisk/core/controller/Dockerfile-debian        |   67 +
 openwhisk/core/controller/Dockerfile.cov           |   30 +
 openwhisk/core/controller/build.gradle             |   50 +
 openwhisk/core/controller/init.sh                  |   24 +
 .../src/main/resources/apiv1swagger.json           | 2915 ++++++++++++++++++
 .../controller/src/main/resources/application.conf |  129 +
 .../controller/src/main/resources/infoswagger.json |  140 +
 .../controller/src/main/resources/reference.conf   |   42 +
 .../apache/openwhisk/core/controller/Actions.scala |  747 +++++
 .../openwhisk/core/controller/Activations.scala    |  237 ++
 .../openwhisk/core/controller/ApiUtils.scala       |  427 +++
 .../core/controller/AuthenticatedRoute.scala       |   28 +
 .../controller/AuthorizedRouteDispatcher.scala     |  183 ++
 .../apache/openwhisk/core/controller/Backend.scala |   41 +
 .../controller/BasicAuthenticationDirective.scala  |   88 +
 .../openwhisk/core/controller/Controller.scala     |  343 +++
 .../openwhisk/core/controller/Entities.scala       |  165 +
 .../apache/openwhisk/core/controller/Limits.scala  |   68 +
 .../openwhisk/core/controller/Namespaces.scala     |   48 +
 .../openwhisk/core/controller/Packages.scala       |  387 +++
 .../openwhisk/core/controller/RestAPIs.scala       |  354 +++
 .../apache/openwhisk/core/controller/Rules.scala   |  439 +++
 .../openwhisk/core/controller/Triggers.scala       |  442 +++
 .../openwhisk/core/controller/WebActions.scala     |  796 +++++
 .../controller/actions/PostActionActivation.scala  |   66 +
 .../core/controller/actions/PrimitiveActions.scala |  708 +++++
 .../core/controller/actions/SequenceActions.scala  |  538 ++++
 .../core/entitlement/ActionCollection.scala        |   58 +
 .../core/entitlement/ActivationThrottler.scala     |   68 +
 .../openwhisk/core/entitlement/Collection.scala    |  168 +
 .../openwhisk/core/entitlement/Entitlement.scala   |  474 +++
 .../core/entitlement/FPCEntitlement.scala          |   84 +
 .../core/entitlement/KindRestrictor.scala          |   57 +
 .../core/entitlement/LocalEntitlement.scala        |   81 +
 .../core/entitlement/PackageCollection.scala       |  145 +
 .../openwhisk/core/entitlement/RateThrottler.scala |   86 +
 .../core/loadBalancer/CommonLoadBalancer.scala     |  371 +++
 .../core/loadBalancer/FPCPoolBalancer.scala        |  764 +++++
 .../openwhisk/core/loadBalancer/FeedFactory.scala  |   28 +
 .../core/loadBalancer/InvokerPoolFactory.scala     |   33 +
 .../core/loadBalancer/InvokerSupervision.scala     |  456 +++
 .../openwhisk/core/loadBalancer/LeanBalancer.scala |   98 +
 .../openwhisk/core/loadBalancer/LoadBalancer.scala |  114 +
 .../ShardingContainerPoolBalancer.scala            |  631 ++++
 .../core/cosmosdb/cache-invalidator/.dockerignore  |    4 +
 .../core/cosmosdb/cache-invalidator/Dockerfile     |   35 +
 .../cosmosdb/cache-invalidator/Dockerfile-debian   |   34 +
 .../core/cosmosdb/cache-invalidator/README.md      |   66 +
 .../core/cosmosdb/cache-invalidator/build.gradle   |   41 +
 .../cosmosdb/cache-invalidator/docker-compose.yml  |   35 +
 openwhisk/core/cosmosdb/cache-invalidator/init.sh  |   25 +
 .../src/main/resources/application.conf            |   26 +
 .../src/main/resources/reference.conf              |   73 +
 .../database/cosmosdb/cache/CacheInvalidator.scala |  112 +
 .../cosmosdb/cache/CacheInvalidatorConfig.scala    |   62 +
 .../cosmosdb/cache/ChangeFeedConsumer.scala        |  142 +
 .../cosmosdb/cache/KafkaEventProducer.scala        |   84 +
 .../core/database/cosmosdb/cache/Main.scala        |   53 +
 .../cosmosdb/cache/WhiskChangeEventObserver.scala  |  117 +
 openwhisk/core/invoker/.dockerignore               |    4 +
 openwhisk/core/invoker/Dockerfile                  |   51 +
 openwhisk/core/invoker/Dockerfile-debian           |   49 +
 openwhisk/core/invoker/Dockerfile.cov              |   29 +
 openwhisk/core/invoker/build.gradle                |   52 +
 openwhisk/core/invoker/init.sh                     |   24 +
 .../invoker/src/main/resources/application.conf    |  195 ++
 .../core/containerpool/ContainerPool.scala         |  712 +++++
 .../core/containerpool/ContainerProxy.scala        | 1188 ++++++++
 .../containerpool/docker/DockerCliLogStore.scala   |   81 +
 .../core/containerpool/docker/DockerClient.scala   |  307 ++
 .../docker/DockerClientWithFileAccess.scala        |  172 ++
 .../containerpool/docker/DockerContainer.scala     |  406 +++
 .../docker/DockerContainerFactory.scala            |  154 +
 .../docker/DockerForMacContainerFactory.scala      |   80 +
 .../core/containerpool/docker/ProcessRunner.scala  |  138 +
 .../core/containerpool/docker/RuncClient.scala     |   98 +
 .../docker/StandaloneDockerContainerFactory.scala  |  117 +
 .../kubernetes/KubernetesClient.scala              |  652 ++++
 .../kubernetes/KubernetesContainer.scala           |  185 ++
 .../kubernetes/KubernetesContainerFactory.scala    |  101 +
 .../containerpool/kubernetes/WhiskPodBuilder.scala |  170 ++
 .../containerpool/v2/ActivationClientProxy.scala   |  440 +++
 .../v2/FunctionPullingContainerPool.scala          |  918 ++++++
 .../v2/FunctionPullingContainerProxy.scala         | 1377 +++++++++
 .../containerpool/v2/InvokerHealthManager.scala    |  367 +++
 .../core/invoker/ContainerMessageConsumer.scala    |  127 +
 .../core/invoker/DefaultInvokerServer.scala        |   68 +
 .../core/invoker/FPCInvokerReactive.scala          |  476 +++
 .../openwhisk/core/invoker/FPCInvokerServer.scala  |   98 +
 .../core/invoker/InstanceIdAssigner.scala          |  104 +
 .../apache/openwhisk/core/invoker/Invoker.scala    |  263 ++
 .../openwhisk/core/invoker/InvokerReactive.scala   |  337 ++
 .../openwhisk/core/invoker/LogStoreCollector.scala |   39 +
 .../core/invoker/NamespaceBlacklist.scala          |   76 +
 .../core/monitoring/user-events/.dockerignore      |    5 +
 openwhisk/core/monitoring/user-events/Dockerfile   |   35 +
 .../core/monitoring/user-events/Dockerfile-debian  |   34 +
 openwhisk/core/monitoring/user-events/README.md    |   77 +
 openwhisk/core/monitoring/user-events/build.gradle |   60 +
 .../compose/grafana/dashboards/global-metrics.json | 1197 ++++++++
 .../grafana/dashboards/openwhisk_events.json       | 1923 ++++++++++++
 .../compose/grafana/dashboards/top-namespaces.json |  473 +++
 .../grafana/provisioning/dashboards/dashboard.yml  |   28 +
 .../provisioning/datasources/datasource.yml        |   67 +
 .../user-events/compose/prometheus/prometheus.yml  |   30 +
 .../monitoring/user-events/images/demo_landing.png |  Bin 0 -> 516563 bytes
 openwhisk/core/monitoring/user-events/init.sh      |   25 +
 .../src/main/resources/application.conf            |   58 +
 .../user-events/src/main/resources/reference.conf  |   51 +
 .../src/main/resources/whisk-logback.xml           |   25 +
 .../core/monitoring/metrics/EventConsumer.scala    |  177 ++
 .../core/monitoring/metrics/KamonRecorder.scala    |  134 +
 .../openwhisk/core/monitoring/metrics/Main.scala   |   42 +
 .../core/monitoring/metrics/MetricNames.scala      |   40 +
 .../core/monitoring/metrics/OpenWhiskEvents.scala  |   71 +
 .../monitoring/metrics/PrometheusEventsApi.scala   |   53 +
 .../monitoring/metrics/PrometheusRecorder.scala    |  266 ++
 .../src/test/resources/application.conf            |   48 +
 .../src/test/resources/logback-test.xml            |   43 +
 .../core/monitoring/metrics/ApiTests.scala         |   87 +
 .../core/monitoring/metrics/EventsTestHelper.scala |   44 +
 .../core/monitoring/metrics/KafkaSpecBase.scala    |   40 +
 .../monitoring/metrics/KamonRecorderTests.scala    |  179 ++
 .../monitoring/metrics/OpenWhiskEventsTests.scala  |   83 +
 .../metrics/PrometheusRecorderTests.scala          |  179 ++
 openwhisk/core/routemgmt/common/apigw-utils.js     | 1054 +++++++
 openwhisk/core/routemgmt/common/utils.js           |  776 +++++
 openwhisk/core/routemgmt/createApi/createApi.js    |  347 +++
 openwhisk/core/routemgmt/createApi/package.json    |    7 +
 openwhisk/core/routemgmt/deleteApi/deleteApi.js    |  244 ++
 openwhisk/core/routemgmt/deleteApi/package.json    |    7 +
 openwhisk/core/routemgmt/getApi/getApi.js          |  180 ++
 openwhisk/core/routemgmt/getApi/package.json       |    7 +
 openwhisk/core/scheduler/Dockerfile                |   35 +
 openwhisk/core/scheduler/Dockerfile.cov            |   30 +
 openwhisk/core/scheduler/build.gradle              |   88 +
 openwhisk/core/scheduler/init.sh                   |   24 +
 openwhisk/core/scheduler/src/main/java/Empty.java  |   22 +
 .../scheduler/src/main/protobuf/activation.proto   |   67 +
 .../scheduler/src/main/resources/application.conf  |  108 +
 .../core/scheduler/FPCSchedulerServer.scala        |  102 +
 .../openwhisk/core/scheduler/Scheduler.scala       |  428 +++
 .../scheduler/container/ContainerManager.scala     |  661 ++++
 .../scheduler/container/CreationJobManager.scala   |  235 ++
 .../scheduler/grpc/ActivationServiceImpl.scala     |  151 +
 .../core/scheduler/message/ContainerMessage.scala  |   86 +
 .../core/scheduler/queue/ContainerCounter.scala    |  121 +
 .../queue/ElasticSearchDurationChecker.scala       |  245 ++
 .../core/scheduler/queue/MemoryQueue.scala         | 1231 ++++++++
 .../core/scheduler/queue/NoopDurationChecker.scala |   47 +
 .../core/scheduler/queue/QueueManager.scala        |  793 +++++
 .../scheduler/queue/SchedulingDecisionMaker.scala  |  282 ++
 openwhisk/core/standalone/Dockerfile               |   34 +
 openwhisk/core/standalone/README.md                |  432 +++
 openwhisk/core/standalone/bin/init                 |   27 +
 openwhisk/core/standalone/bin/stop                 |   18 +
 openwhisk/core/standalone/bin/waitready            |   27 +
 openwhisk/core/standalone/build.gradle             |  183 ++
 .../src/main/resources/logback-standalone.xml      |   38 +
 .../playground/actions/playground-delete.js        |   34 +
 .../playground/actions/playground-fetch.js         |   34 +
 .../resources/playground/actions/playground-run.js |   72 +
 .../playground/actions/playground-userpackage.js   |   60 +
 .../src/main/resources/playground/ui/index.html    |  139 +
 .../main/resources/playground/ui/playground.css    |  228 ++
 .../resources/playground/ui/playgroundFunctions.js |  799 +++++
 .../src/main/resources/standalone-kcf.conf         |   38 +
 .../standalone/src/main/resources/standalone.conf  |  149 +
 .../openwhisk/core/ExecManifestSupport.scala       |   30 +
 .../openwhisk/standalone/ApiGwLauncher.scala       |  118 +
 .../openwhisk/standalone/CouchDBLauncher.scala     |  266 ++
 .../openwhisk/standalone/DockerVersion.scala       |   50 +
 .../openwhisk/standalone/InstallRouteMgmt.scala    |  107 +
 .../openwhisk/standalone/KafkaLauncher.scala       |  123 +
 .../openwhisk/standalone/LogbackConfigurator.scala |   92 +
 .../openwhisk/standalone/PlaygroundLauncher.scala  |  135 +
 .../openwhisk/standalone/PreFlightChecks.scala     |  181 ++
 .../openwhisk/standalone/ServerStartupCheck.scala  |   45 +
 .../openwhisk/standalone/ServiceInfoLogger.scala   |   49 +
 .../standalone/StandaloneDockerSupport.scala       |  243 ++
 .../openwhisk/standalone/StandaloneOpenWhisk.scala |  590 ++++
 .../org/apache/openwhisk/standalone/Unzip.scala    |   47 +
 .../openwhisk/standalone/UserEventLauncher.scala   |  156 +
 .../org/apache/openwhisk/standalone/Wsk.scala      |   81 +
 .../openwhisk/standalone/DockerVersionTests.scala  |   43 +
 openwhisk/core/standalone/start.sh                 |   31 +
 openwhisk/core/standalone/stop.sh                  |   18 +
 openwhisk/docs/README.md                           |   95 +
 openwhisk/docs/about.md                            |  167 +
 openwhisk/docs/actions-actionloop.md               |  805 +++++
 openwhisk/docs/actions-docker.md                   |  334 ++
 openwhisk/docs/actions-dotnet.md                   |  147 +
 openwhisk/docs/actions-go.md                       |  228 ++
 openwhisk/docs/actions-java.md                     |  123 +
 openwhisk/docs/actions-new.md                      |  292 ++
 openwhisk/docs/actions-nodejs.md                   |  503 +++
 openwhisk/docs/actions-php.md                      |  157 +
 openwhisk/docs/actions-python.md                   |  169 ++
 openwhisk/docs/actions-ruby.md                     |  108 +
 openwhisk/docs/actions-rust.md                     |  151 +
 openwhisk/docs/actions-swift.md                    |  239 ++
 openwhisk/docs/actions-update.md                   |   75 +
 openwhisk/docs/actions.md                          |  753 +++++
 openwhisk/docs/annotations.md                      |  125 +
 openwhisk/docs/apigateway.md                       |  251 ++
 openwhisk/docs/catalog.md                          |   49 +
 openwhisk/docs/cli.md                              |  129 +
 openwhisk/docs/concurrency.md                      |   55 +
 openwhisk/docs/conductors.md                       |  271 ++
 openwhisk/docs/dedicated-invokers.md               |   40 +
 openwhisk/docs/deploy.md                           |   59 +
 openwhisk/docs/dev/configuration.md                |   35 +
 openwhisk/docs/dev/future.md                       |   29 +
 openwhisk/docs/dev/modules.md                      |  142 +
 .../docs/dev/troubleshooting/build-failures.md     |   41 +
 openwhisk/docs/feeds.md                            |  125 +
 .../images/IoT_solution_architecture_example.png   |  Bin 0 -> 218584 bytes
 openwhisk/docs/images/OpenWhisk.png                |  Bin 0 -> 95551 bytes
 .../OpenWhisk_flow_of_processing.draw.io.xml       |    1 +
 .../docs/images/OpenWhisk_flow_of_processing.png   |  Bin 0 -> 46130 bytes
 openwhisk/docs/images/Openwhisk-flow-diagram.png   |  Bin 0 -> 130358 bytes
 openwhisk/docs/images/README.md                    |   20 +
 openwhisk/docs/images/apache-openwhisk-stacked.svg |    1 +
 openwhisk/docs/images/apache-openwhisk-wide.svg    |    1 +
 openwhisk/docs/images/components_to_repos.png      |  Bin 0 -> 274201 bytes
 openwhisk/docs/images/playground-ui.png            |  Bin 0 -> 62459 bytes
 ...hisk_icon_full-color_with_tm_128x128-300dpi.png |  Bin 0 -> 113623 bytes
 ...hisk_icon_full-color_with_tm_210x138-300dpi.png |  Bin 0 -> 164022 bytes
 .../whisk_icon_full-color_with_tm_64x64-300dpi.png |  Bin 0 -> 64400 bytes
 .../docs/images/whisk_icon_full_color_with_tm.psd  |  Bin 0 -> 89698 bytes
 ...hisk_icon_full_color_with_tm_100x100-300dpi.png |  Bin 0 -> 88054 bytes
 ...whisk_icon_full_color_with_tm_100x100-96dpi.png |  Bin 0 -> 88052 bytes
 ...hisk_icon_full_color_with_tm_200x200-300dpi.png |  Bin 0 -> 208169 bytes
 openwhisk/docs/logging.md                          |   61 +
 openwhisk/docs/metrics.md                          |  368 +++
 openwhisk/docs/packages.md                         |  457 +++
 openwhisk/docs/parameters.md                       |  201 ++
 openwhisk/docs/reference.md                        |  148 +
 openwhisk/docs/rest_api.md                         |  346 +++
 openwhisk/docs/samples.md                          |   78 +
 openwhisk/docs/security.md                         |   37 +
 openwhisk/docs/single_entrypoint_proxy_contract.md |  385 +++
 openwhisk/docs/spi.md                              |   94 +
 openwhisk/docs/tag-based-scheduling.md             |   57 +
 openwhisk/docs/triggers_rules.md                   |  177 ++
 openwhisk/docs/use_cases.md                        |   70 +
 openwhisk/docs/warmed-containers.md                |   52 +
 openwhisk/docs/webactions.md                       |  519 ++++
 openwhisk/docs/yarn.md                             |   84 +
 openwhisk/gradle/README.md                         |   64 +
 openwhisk/gradle/docker.gradle                     |  171 ++
 openwhisk/gradle/wrapper/gradle-wrapper.jar        |  Bin 0 -> 59203 bytes
 openwhisk/gradle/wrapper/gradle-wrapper.properties |   22 +
 openwhisk/gradlew                                  |  185 ++
 openwhisk/gradlew.bat                              |   89 +
 openwhisk/licenses/LICENSE-spray.txt               |   15 +
 .../POEM-1-proposal-for-openwhisk-enhancements.md  |   65 +
 .../POEM-2-function-pulling-container-scheduler.md |  126 +
 .../proposals/POEM-3-action-limit-for-namespace.md |  283 ++
 ...-4-action-concurrency-limit-within-namespace.md |   82 +
 openwhisk/proposals/POEM-N-template.md             |   58 +
 openwhisk/proposals/POEM-support-array-result.md   |   70 +
 openwhisk/proposals/README.md                      |   40 +
 openwhisk/settings.gradle                          |   82 +
 openwhisk/tests/.pydevproject                      |    5 +
 openwhisk/tests/README.md                          |   50 +
 openwhisk/tests/build.gradle                       |  423 +++
 openwhisk/tests/dat/actions/README.md              |   36 +
 openwhisk/tests/dat/actions/applicationError.js    |   20 +
 openwhisk/tests/dat/actions/argCheck.js            |   20 +
 openwhisk/tests/dat/actions/argsPrint.js           |   22 +
 openwhisk/tests/dat/actions/asyncError.js          |   20 +
 openwhisk/tests/dat/actions/blackbox/exec          |   20 +
 openwhisk/tests/dat/actions/build.sh               |   41 +
 openwhisk/tests/dat/actions/cat.js                 |   28 +
 openwhisk/tests/dat/actions/concurrent.js          |   53 +
 openwhisk/tests/dat/actions/concurrentFail1.js     |   34 +
 openwhisk/tests/dat/actions/concurrentFail2.js     |   34 +
 openwhisk/tests/dat/actions/conductor.js           |   32 +
 openwhisk/tests/dat/actions/corsHeaderMod.js       |   29 +
 openwhisk/tests/dat/actions/countdown.js           |   37 +
 openwhisk/tests/dat/actions/createRule.swift       |   30 +
 openwhisk/tests/dat/actions/createTrigger.swift    |   24 +
 openwhisk/tests/dat/actions/dosLogs.js             |   25 +
 openwhisk/tests/dat/actions/echo-web-http-head.js  |   23 +
 openwhisk/tests/dat/actions/echo-web-http.js       |   24 +
 openwhisk/tests/dat/actions/echo.js                |   23 +
 openwhisk/tests/dat/actions/echo.json              |   16 +
 openwhisk/tests/dat/actions/empty.js               |    0
 openwhisk/tests/dat/actions/emptyJSONResult.js     |   19 +
 openwhisk/tests/dat/actions/head.js                |   29 +
 openwhisk/tests/dat/actions/hello.js               |   25 +
 openwhisk/tests/dat/actions/hello.py               |   27 +
 openwhisk/tests/dat/actions/hello.swift            |   27 +
 openwhisk/tests/dat/actions/helloArray.js          |   24 +
 openwhisk/tests/dat/actions/helloAsync.js          |   36 +
 openwhisk/tests/dat/actions/helloContext.js        |   28 +
 openwhisk/tests/dat/actions/helloDeadline.js       |   41 +
 .../tests/dat/actions/helloOpenwhiskPackage.js     |   43 +
 openwhisk/tests/dat/actions/helloPromise.js        |   26 +
 openwhisk/tests/dat/actions/httpGet.swift          |   70 +
 openwhisk/tests/dat/actions/initexit.js            |   18 +
 openwhisk/tests/dat/actions/initforever.js         |   18 +
 openwhisk/tests/dat/actions/invalidInput1.json     |    3 +
 openwhisk/tests/dat/actions/invalidInput2.json     |    4 +
 openwhisk/tests/dat/actions/invalidInput3.json     |    2 +
 openwhisk/tests/dat/actions/invalidInput4.json     |    3 +
 openwhisk/tests/dat/actions/invoke.swift           |   33 +
 .../tests/dat/actions/invokeNonBlocking.swift      |   33 +
 openwhisk/tests/dat/actions/jsonStringWebAction.js |   26 +
 openwhisk/tests/dat/actions/log.js                 |   24 +
 openwhisk/tests/dat/actions/loggingTimeout.js      |   50 +
 openwhisk/tests/dat/actions/malformed.js           |   18 +
 openwhisk/tests/dat/actions/malformed.py           |   19 +
 openwhisk/tests/dat/actions/memoryWithGC.js        |  106 +
 openwhisk/tests/dat/actions/multipleHeaders.js     |   25 +
 openwhisk/tests/dat/actions/niam.js                |   20 +
 openwhisk/tests/dat/actions/niam.py                |   23 +
 openwhisk/tests/dat/actions/niam.swift             |   21 +
 openwhisk/tests/dat/actions/openFiles.js           |   56 +
 openwhisk/tests/dat/actions/params.js              |   20 +
 openwhisk/tests/dat/actions/ping.js                |   46 +
 openwhisk/tests/dat/actions/pngWeb.js              |   30 +
 openwhisk/tests/dat/actions/printParams.js         |   42 +
 openwhisk/tests/dat/actions/python-zip/__main__.py |   22 +
 openwhisk/tests/dat/actions/python-zip/greet.py    |   25 +
 openwhisk/tests/dat/actions/pythonVersion.py       |   25 +
 openwhisk/tests/dat/actions/runexception.js        |   20 +
 openwhisk/tests/dat/actions/runexit.js             |   20 +
 openwhisk/tests/dat/actions/sizedResult.js         |   23 +
 openwhisk/tests/dat/actions/sleep.js               |   57 +
 openwhisk/tests/dat/actions/sleep.py               |   38 +
 openwhisk/tests/dat/actions/sort-array.js          |   28 +
 openwhisk/tests/dat/actions/sort.js                |   30 +
 openwhisk/tests/dat/actions/split-array.js         |   31 +
 openwhisk/tests/dat/actions/split.js               |   31 +
 .../tests/dat/actions/src/java/sleep/build.gradle  |   30 +
 .../dat/actions/src/java/sleep/settings.gradle     |   18 +
 .../src/java/sleep/src/main/java/Sleep.java        |   52 +
 openwhisk/tests/dat/actions/stdenv.py              |   24 +
 openwhisk/tests/dat/actions/step.js                |   24 +
 openwhisk/tests/dat/actions/textBody.js            |   25 +
 openwhisk/tests/dat/actions/trigger.swift          |   25 +
 .../tests/dat/actions/unicode.tests/go-1.13.txt    |   29 +
 .../tests/dat/actions/unicode.tests/go-1.15.txt    |   29 +
 .../tests/dat/actions/unicode.tests/nodejs-10.txt  |   22 +
 .../tests/dat/actions/unicode.tests/nodejs-12.txt  |   22 +
 .../tests/dat/actions/unicode.tests/nodejs-14.txt  |   22 +
 .../tests/dat/actions/unicode.tests/php-7.3.txt    |   23 +
 .../tests/dat/actions/unicode.tests/php-7.4.txt    |   23 +
 .../tests/dat/actions/unicode.tests/python-3.txt   |   24 +
 .../tests/dat/actions/unicode.tests/python.txt     |   24 +
 .../tests/dat/actions/unicode.tests/ruby-2.5.txt   |   22 +
 .../tests/dat/actions/unicode.tests/rust-1.34.txt  |   40 +
 .../Apache.OpenWhisk.UnicodeTests.Dotnet.csproj    |   27 +
 .../Unicode.cs                                     |   35 +
 .../dotnet2.2/openwhisk-unicodetests-dotnet.sln    |   33 +
 .../unicode.tests/src/java/unicode/build.gradle    |   30 +
 .../unicode.tests/src/java/unicode/settings.gradle |   18 +
 .../src/java/unicode/src/main/java/Unicode.java    |   29 +
 .../tests/dat/actions/unicode.tests/swift-4.2.txt  |   27 +
 .../tests/dat/actions/unicode.tests/swift-5.1.txt  |   27 +
 .../tests/dat/actions/unicode.tests/swift-5.3.txt  |   27 +
 openwhisk/tests/dat/actions/validInput1.json       |   11 +
 openwhisk/tests/dat/actions/validInput2.json       |    3 +
 openwhisk/tests/dat/actions/wc.js                  |   27 +
 openwhisk/tests/dat/actions/wcbin.js               |   43 +
 openwhisk/tests/dat/actions/word_count.json        |   16 +
 openwhisk/tests/dat/actions/zippedaction/index.js  |   24 +
 .../dat/actions/zippedaction/package-lock.json     |   21 +
 .../tests/dat/actions/zippedaction/package.json    |   11 +
 ..._path_param_support_test_invalidActionType.json |   62 +
 ..._path_param_support_test_invalidParamName1.json |   62 +
 ..._path_param_support_test_invalidParamName2.json |   62 +
 ...w_path_param_support_test_invalidTargetUrl.json |   62 +
 ...ath_param_support_test_withPathParameters1.json |   62 +
 ...ath_param_support_test_withPathParameters2.json |   70 +
 .../apigw/endpoints.without.action.swagger.json    |   76 +
 openwhisk/tests/dat/apigw/local.api.bad.yaml       |   51 +
 openwhisk/tests/dat/apigw/local.api.yaml           |   47 +
 openwhisk/tests/dat/apigw/testswaggerdoc1          |  139 +
 openwhisk/tests/dat/apigw/testswaggerdoc2          |  117 +
 openwhisk/tests/dat/apigw/testswaggerdocinvalid    |   12 +
 openwhisk/tests/performance/README.md              |  185 ++
 .../tests/performance/gatling_tests/build.gradle   |   40 +
 openwhisk/tests/performance/gatling_tests/build.sh |   31 +
 .../src/gatling/resources/conf/logback.xml         |   26 +
 .../src/gatling/resources/data/nodeJSAction.js     |   22 +
 .../gatling/resources/data/nodeJSAsyncAction.js    |   26 +
 .../src/gatling/resources/data/pythonAction.py     |   25 +
 .../gatling/resources/data/src/java/build.gradle   |   35 +
 .../resources/data/src/java/settings.gradle        |   18 +
 .../data/src/java/src/main/java/JavaAction.java    |   40 +
 .../src/gatling/resources/data/swiftAction.swift   |   26 +
 .../src/gatling/resources/data/users.csv           |    3 +
 .../org/apache/openwhisk/ApiV1Simulation.scala     |   55 +
 .../BlockingInvokeOneActionSimulation.scala        |   84 +
 .../openwhisk/ColdBlockingInvokeSimulation.scala   |  100 +
 .../org/apache/openwhisk/LatencySimulation.scala   |  127 +
 .../extension/whisk/OpenWhiskActionBuilder.scala   |  126 +
 .../openwhisk/extension/whisk/OpenWhiskDsl.scala   |   26 +
 .../extension/whisk/OpenWhiskProtocolBuilder.scala |   81 +
 .../apache/openwhisk/extension/whisk/Predef.scala  |   20 +
 .../tests/performance/preparation/actions/async.js |   24 +
 .../tests/performance/preparation/actions/noop.js  |   20 +
 openwhisk/tests/performance/preparation/create.sh  |   46 +
 .../tests/performance/preparation/deploy-lean.sh   |   38 +
 openwhisk/tests/performance/preparation/deploy.sh  |   39 +
 openwhisk/tests/performance/wrk_tests/latency.sh   |   30 +
 openwhisk/tests/performance/wrk_tests/post.lua     |   17 +
 .../tests/performance/wrk_tests/throughput.sh      |   54 +
 .../org/apache/openwhisk/GradleWorkaround.scala    |   23 +
 .../tests/src/test/resources/application.conf.j2   |  179 ++
 .../tests/src/test/resources/logback-test.xml      |   32 +
 .../tests/src/test/resources/swagger-config.json   |    5 +
 .../test/resources/templates/build.gradle.mustache |  103 +
 .../scala/actionContainers/ActionContainer.scala   |  278 ++
 .../actionContainers/BasicActionRunnerTests.scala  |  379 +++
 .../scala/actionContainers/ResourceHelpers.scala   |  190 ++
 .../apigw/healthtests/ApiGwEndToEndTests.scala     |  201 ++
 .../apigw/healthtests/ApiGwRestEndToEndTests.scala |   94 +
 .../src/test/scala/common/ConcurrencyHelpers.scala |   30 +
 .../src/test/scala/common/FreePortFinder.scala     |   33 +
 .../tests/src/test/scala/common/JsHelpers.scala    |   39 +
 .../src/test/scala/common/LoggedFunction.scala     |  171 ++
 openwhisk/tests/src/test/scala/common/Pair.java    |   28 +
 .../tests/src/test/scala/common/RunCliCmd.scala    |  152 +
 .../tests/src/test/scala/common/SimpleExec.scala   |   58 +
 .../src/test/scala/common/StreamLogging.scala      |   39 +
 .../tests/src/test/scala/common/TestHelpers.scala  |   35 +
 .../tests/src/test/scala/common/TestUtils.java     |  346 +++
 .../src/test/scala/common/TimingHelpers.scala      |   33 +
 .../src/test/scala/common/WhiskProperties.java     |  434 +++
 .../src/test/scala/common/WskActorSystem.scala     |   52 +
 .../src/test/scala/common/WskCliOperations.scala   |  978 ++++++
 .../src/test/scala/common/WskOperations.scala      |  384 +++
 .../src/test/scala/common/WskTestHelpers.scala     |  340 +++
 .../src/test/scala/common/WskTracingTests.scala    |  135 +
 .../test/scala/common/rest/SwaggerValidator.scala  |  116 +
 .../test/scala/common/rest/WskRestOperations.scala | 1441 +++++++++
 .../src/test/scala/ha/CacheInvalidationTests.scala |  188 ++
 .../src/test/scala/ha/ShootComponentsTests.scala   |  332 ++
 .../scala/invokerShoot/ShootInvokerTests.scala     |  354 +++
 .../src/test/scala/limits/ThrottleTests.scala      |  493 +++
 .../openwhisk/common/ConfigMXBeanTests.scala       |   39 +
 .../openwhisk/common/ConfigMapValueTests.scala     |   71 +
 .../org/apache/openwhisk/common/ConfigTests.scala  |   74 +
 .../openwhisk/common/ForcibleSemaphoreTests.scala  |   95 +
 .../openwhisk/common/NestedSemaphoreTests.scala    |   73 +
 .../apache/openwhisk/common/PrometheusTests.scala  |   86 +
 .../openwhisk/common/ResizableSemaphoreTests.scala |  190 ++
 .../apache/openwhisk/common/RunCliCmdTests.scala   |   90 +
 .../apache/openwhisk/common/SchedulerTests.scala   |  230 ++
 .../openwhisk/common/TransactionIdTests.scala      |  111 +
 .../apache/openwhisk/common/UserEventTests.scala   |   97 +
 .../openwhisk/common/etcd/EtcdConfigTests.scala    |   35 +
 .../apache/openwhisk/common/etcd/EtcdKvTests.scala |   74 +
 .../common/etcd/EtcdLeaderShipUnitTests.scala      |  179 ++
 .../openwhisk/common/etcd/EtcdWorkerTests.scala    |  176 ++
 .../connector/kafka/KafkaMetricsTests.scala        |   54 +
 .../apache/openwhisk/core/WhiskConfigTests.scala   |   85 +
 .../openwhisk/core/admin/WskAdminTests.scala       |  261 ++
 .../test/ApiGwRestRoutemgmtActionTests.scala       |   31 +
 .../actions/test/ApiGwRoutemgmtActionTests.scala   |  380 +++
 .../core/cli/test/ApiGwRestBasicTests.scala        |  938 ++++++
 .../openwhisk/core/cli/test/ApiGwRestTests.scala   |  305 ++
 .../openwhisk/core/cli/test/BaseApiGwTests.scala   |  161 +
 .../openwhisk/core/cli/test/TestJsonArgs.scala     |  103 +
 .../core/cli/test/WskActionSequenceTests.scala     |   91 +
 .../core/cli/test/WskEntitlementTests.scala        |  419 +++
 .../core/cli/test/WskRestActionSequenceTests.scala |   37 +
 .../core/cli/test/WskRestBasicUsageTests.scala     |  829 +++++
 .../core/cli/test/WskRestEntitlementTests.scala    |   74 +
 .../core/cli/test/WskWebActionsTests.scala         |  417 +++
 .../core/connector/test/EventMessageTests.scala    |  145 +
 .../core/connector/test/MessageFeedTests.scala     |  188 ++
 .../core/connector/test/MessageTests.scala         |   87 +
 .../core/connector/test/TestConnector.scala        |  104 +
 .../tests/AcknowledgementMessageTests.scala        |  138 +
 .../docker/test/AkkaContainerClientTests.scala     |  230 ++
 .../test/ApacheBlockingContainerClientTests.scala  |  182 ++
 .../docker/test/DockerClientTests.scala            |  403 +++
 .../test/DockerClientWithFileAccessTests.scala     |  153 +
 .../docker/test/DockerContainerFactoryTests.scala  |  151 +
 .../docker/test/DockerContainerTests.scala         |  872 ++++++
 .../docker/test/ProcessRunnerTests.scala           |  105 +
 .../docker/test/RuncClientTests.scala              |  102 +
 .../kubernetes/test/Fabric8ClientTests.scala       |  105 +
 .../kubernetes/test/KubeClientSupport.scala        |   72 +
 .../kubernetes/test/KubernetesClientTests.scala    |  240 ++
 .../kubernetes/test/KubernetesContainerTests.scala |  571 ++++
 .../kubernetes/test/WhiskPodBuilderTests.scala     |  261 ++
 .../logging/ElasticSearchLogStoreTests.scala       |  258 ++
 .../logging/ElasticSearchRestClientTests.scala     |  239 ++
 .../logging/LogDriverLogStoreTests.scala           |   49 +
 .../logging/SplunkLogStoreTests.scala              |  194 ++
 .../test/DockerToActivationFileLogStoreTests.scala |  137 +
 .../test/DockerToActivationLogStoreTests.scala     |  191 ++
 .../test/ContainerArgsConfigTest.scala             |   69 +
 .../containerpool/test/ContainerPoolTests.scala    | 1321 ++++++++
 .../containerpool/test/ContainerProxyTests.scala   | 2266 ++++++++++++++
 .../v2/test/ActivationClientProxyTests.scala       |  476 +++
 .../test/FunctionPullingContainerPoolTests.scala   | 1285 ++++++++
 .../test/FunctionPullingContainerProxyTests.scala  | 3203 ++++++++++++++++++++
 .../v2/test/InvokerHealthManagerTests.scala        |  452 +++
 .../core/containerpool/yarn/test/MockYARNRM.scala  |  247 ++
 .../yarn/test/YARNContainerFactoryTests.scala      |  528 ++++
 .../actions/test/SequenceAccountingTests.scala     |  137 +
 .../core/controller/test/ActionsApiTests.scala     | 1738 +++++++++++
 .../test/ActionsApiWithDbPollingTests.scala        |  128 +
 .../test/ActionsApiWithoutDbPollingTests.scala     |  125 +
 .../core/controller/test/ActivationsApiTests.scala |  855 ++++++
 .../controller/test/BasicAuthenticateTests.scala   |  162 +
 .../core/controller/test/ConductorsApiTests.scala  |  395 +++
 .../core/controller/test/ControllerApiTests.scala  |   93 +
 .../controller/test/ControllerRoutesTests.scala    |   81 +
 .../controller/test/ControllerTestCommon.scala     |  346 +++
 .../controller/test/EntitlementProviderTests.scala |  714 +++++
 .../core/controller/test/FPCEntitlementTests.scala |   61 +
 .../core/controller/test/KindRestrictorTests.scala |   85 +
 .../core/controller/test/LimitsApiTests.scala      |  117 +
 .../core/controller/test/NamespacesApiTests.scala  |   74 +
 .../controller/test/PackageActionsApiTests.scala   |  671 ++++
 .../core/controller/test/PackagesApiTests.scala    |  963 ++++++
 .../core/controller/test/RateThrottleTests.scala   |   67 +
 .../controller/test/RespondWithHeadersTests.scala  |   91 +
 .../core/controller/test/RulesApiTests.scala       | 1114 +++++++
 .../core/controller/test/SequenceApiTests.scala    |  443 +++
 .../core/controller/test/SwaggerRoutesTests.scala  |   60 +
 .../core/controller/test/TriggersApiTests.scala    |  491 +++
 .../core/controller/test/WebActionsApiTests.scala  | 2050 +++++++++++++
 .../core/controller/test/WhiskAuthHelpers.scala    |   35 +
 .../SequenceActionApiMigrationTests.scala          |  179 ++
 .../ArtifactActivationStoreBehaviorBase.scala      |   32 +
 .../database/ArtifactActivationStoreTests.scala    |   57 +
 ...tifactWithFileStorageActivationStoreTests.scala |  350 +++
 .../core/database/CouchDBArtifactStoreTests.scala  |   26 +
 .../database/CouchDBAttachmentStoreTests.scala     |   32 +
 .../core/database/CouchDBStoreBehaviorBase.scala   |   58 +
 .../core/database/LimitsCommandTests.scala         |  133 +
 .../openwhisk/core/database/UserCommandTests.scala |  332 ++
 .../core/database/WhiskAdminCliTestBase.scala      |   73 +
 .../openwhisk/core/database/azblob/AzureBlob.scala |   61 +
 .../AzureBlobAttachmentStoreBehaviorBase.scala     |   51 +
 .../azblob/AzureBlobAttachmentStoreCDNTests.scala  |   47 +
 .../azblob/AzureBlobAttachmentStoreITTests.scala   |   29 +
 .../core/database/azblob/AzureBlobConfigTest.scala |   40 +
 .../cosmosdb/CollectionResourceUsageTests.scala    |   45 +
 .../cosmosdb/CosmosDBArtifactStoreTests.scala      |  196 ++
 .../cosmosdb/CosmosDBAttachmentStoreTests.scala    |   36 +
 .../database/cosmosdb/CosmosDBConfigTests.scala    |  133 +
 .../core/database/cosmosdb/CosmosDBLeakTests.scala |   97 +
 .../cosmosdb/CosmosDBSoftDeleteTests.scala         |   43 +
 .../cosmosdb/CosmosDBStoreBehaviorBase.scala       |   66 +
 .../database/cosmosdb/CosmosDBSupportTests.scala   |  125 +
 .../database/cosmosdb/CosmosDBTestSupport.scala    |   88 +
 .../core/database/cosmosdb/CosmosDBUtilTest.scala  |   83 +
 .../database/cosmosdb/IndexingPolicyTests.scala    |   63 +
 .../database/cosmosdb/RecordingLeakDetector.java   |   41 +
 .../cosmosdb/RecordingLeakDetectorFactory.java     |   35 +
 .../database/cosmosdb/ReferenceCountedTests.scala  |   91 +
 .../cosmosdb/cache/CacheInvalidatorTests.scala     |  228 ++
 .../cache/WhiskChangeEventObserverTests.scala      |   83 +
 .../ElasticSearchActivationStoreBehaviorBase.scala |   58 +
 .../ElasticSearchActivationStoreTests.scala        |   66 +
 .../memory/MemoryArtifactStoreBehaviorBase.scala   |   65 +
 .../database/memory/MemoryArtifactStoreTests.scala |   26 +
 .../memory/MemoryAttachmentStoreTests.scala        |   45 +
 .../mongodb/MongoDBArtifactStoreTests.scala        |   26 +
 .../mongodb/MongoDBAsyncStreamGraphTests.scala     |  134 +
 .../mongodb/MongoDBAttachmentStoreTests.scala      |   33 +
 .../mongodb/MongoDBStoreBehaviorBase.scala         |   67 +
 .../database/mongodb/MongoDBViewMapperTests.scala  |  256 ++
 .../core/database/s3/CloudFrontSignerTests.scala   |   79 +
 .../database/s3/S3AttachmentStoreAwsTests.scala    |   29 +
 .../s3/S3AttachmentStoreBehaviorBase.scala         |   51 +
 .../s3/S3AttachmentStoreCloudFrontTests.scala      |   47 +
 .../database/s3/S3AttachmentStoreMinioTests.scala  |   31 +
 .../apache/openwhisk/core/database/s3/S3Aws.scala  |   75 +
 .../openwhisk/core/database/s3/S3Minio.scala       |  116 +
 .../core/database/s3/S3WithPrefixTests.scala       |   39 +
 .../test/AttachmentCompatibilityTests.scala        |  251 ++
 .../database/test/AttachmentStoreBehaviors.scala   |  198 ++
 .../database/test/AttachmentSupportTests.scala     |   53 +
 .../core/database/test/BatcherTests.scala          |  193 ++
 .../core/database/test/CacheConcurrencyTests.scala |  158 +
 .../database/test/CleanUpActivationsTest.scala     |  146 +
 .../database/test/CouchDbRestClientTests.scala     |  308 ++
 .../database/test/DatabaseScriptTestUtils.scala    |  120 +
 .../openwhisk/core/database/test/DbUtils.scala     |  363 +++
 .../core/database/test/DocumentHandlerTests.scala  |  592 ++++
 .../database/test/ExtendedCouchDbRestClient.scala  |   82 +
 .../MultipleReadersSingleWriterCacheTests.scala    |   76 +
 .../core/database/test/RemoveLogsTests.scala       |  114 +
 .../core/database/test/ReplicatorTests.scala       |  489 +++
 .../test/behavior/ActivationStoreBehavior.scala    |   23 +
 .../behavior/ActivationStoreBehaviorBase.scala     |  115 +
 .../behavior/ActivationStoreCRUDBehaviors.scala    |   81 +
 .../behavior/ActivationStoreQueryBehaviors.scala   |  370 +++
 .../ArtifactStoreActivationsQueryBehaviors.scala   |   55 +
 .../ArtifactStoreAttachmentBehaviors.scala         |  227 ++
 .../test/behavior/ArtifactStoreBehavior.scala      |   27 +
 .../test/behavior/ArtifactStoreBehaviorBase.scala  |  188 ++
 .../test/behavior/ArtifactStoreCRUDBehaviors.scala |  217 ++
 .../behavior/ArtifactStoreQueryBehaviors.scala     |  282 ++
 .../ArtifactStoreSubjectQueryBehaviors.scala       |  206 ++
 .../test/behavior/ArtifactStoreTestUtil.scala      |   36 +
 .../ArtifactStoreWhisksQueryBehaviors.scala        |   86 +
 .../core/entity/test/ActivationCompatTests.scala   |  207 ++
 .../core/entity/test/ActivationResponseTests.scala |  197 ++
 .../entity/test/ControllerInstanceIdTests.scala    |   61 +
 .../core/entity/test/DatastoreTests.scala          |  354 +++
 .../openwhisk/core/entity/test/ExecHelpers.scala   |  128 +
 .../core/entity/test/ExecManifestTests.scala       |  484 +++
 .../openwhisk/core/entity/test/ExecTests.scala     |  309 ++
 .../core/entity/test/InvokerInstanceIdTests.scala  |   75 +
 .../core/entity/test/MigrationEntities.scala       |   89 +
 .../entity/test/ParameterEncryptionTests.scala     |  262 ++
 .../openwhisk/core/entity/test/SchemaTests.scala   |  957 ++++++
 .../openwhisk/core/entity/test/SizeTests.scala     |  215 ++
 .../openwhisk/core/entity/test/ViewTests.scala     |  421 +++
 .../core/entity/test/WhiskEntityTests.scala        |  227 ++
 .../test/ContainerMessageConsumerTests.scala       |  324 ++
 .../invoker/test/DefaultInvokerServerTests.scala   |  176 ++
 .../core/invoker/test/FPCInvokerServerTests.scala  |  175 ++
 .../invoker/test/InstanceIdAssignerTests.scala     |   72 +
 .../core/invoker/test/InvokerBootUpTests.scala     |   92 +
 .../invoker/test/NamespaceBlacklistTests.scala     |  111 +
 .../openwhisk/core/limits/ActionLimitsTests.scala  |  509 ++++
 .../openwhisk/core/limits/ConcurrencyTests.scala   |  215 ++
 .../core/limits/MaxActionDurationTests.scala       |   99 +
 .../loadBalancer/test/FPCPoolBalancerTests.scala   |  299 ++
 .../test/InvokerSupervisionTests.scala             |  469 +++
 .../test/ShardingContainerPoolBalancerTests.scala  |  575 ++++
 .../core/scheduler/FPCSchedulerServerTests.scala   |  154 +
 .../container/test/ContainerManagerTests.scala     | 1577 ++++++++++
 .../container/test/CreationJobManagerTests.scala   |  419 +++
 .../grpc/test/ActivationServiceImplTests.scala     |  273 ++
 .../core/scheduler/grpc/test/CommonVariable.scala  |   40 +
 .../queue/test/ContainerCounterTests.scala         |  393 +++
 ...lasticSearchDurationCheckResultFormatTest.scala |  124 +
 .../test/ElasticSearchDurationCheckerTests.scala   |  404 +++
 .../queue/test/MemoryQueueFlowTests.scala          | 1819 +++++++++++
 .../scheduler/queue/test/MemoryQueueTests.scala    | 2173 +++++++++++++
 .../queue/test/MemoryQueueTestsFixture.scala       |  314 ++
 .../scheduler/queue/test/QueueManagerTests.scala   | 1206 ++++++++
 .../queue/test/SchedulingDecisionMakerTests.scala  |  896 ++++++
 .../core/service/DataManagementServiceTests.scala  |  287 ++
 .../core/service/LeaseKeepAliveServiceTests.scala  |  232 ++
 .../core/service/WatcherServiceTests.scala         |  306 ++
 .../openwhisk/http/PoolingRestClientTests.scala    |  184 ++
 .../scala/org/apache/openwhisk/spi/SpiTests.scala  |   56 +
 .../standalone/StandaloneApiGwTests.scala          |   43 +
 .../standalone/StandaloneCouchTests.scala          |   38 +
 .../openwhisk/standalone/StandaloneKCFTests.scala  |   82 +
 .../standalone/StandaloneKafkaTests.scala          |   33 +
 .../standalone/StandaloneSanityTestSupport.scala   |   33 +
 .../standalone/StandaloneServerFixture.scala       |  170 ++
 .../standalone/StandaloneServerTests.scala         |   28 +
 .../standalone/StandaloneUserEventTests.scala      |   46 +
 .../org/apache/openwhisk/test/http/RESTProxy.scala |  131 +
 .../utils/test/ExecutionContextFactoryTests.scala  |   45 +
 .../src/test/scala/services/HeadersTests.scala     |  275 ++
 .../test/scala/services/KafkaConnectorTests.scala  |  183 ++
 .../test/scala/system/basic/WskActionTests.scala   |  409 +++
 .../system/basic/WskActivationLogsTests.scala      |   59 +
 .../scala/system/basic/WskActivationTests.scala    |   82 +
 .../scala/system/basic/WskConductorTests.scala     |  393 +++
 .../test/scala/system/basic/WskConsoleTests.scala  |  122 +
 .../scala/system/basic/WskMultiRuntimeTests.scala  |   60 +
 .../test/scala/system/basic/WskPackageTests.scala  |  190 ++
 .../scala/system/basic/WskRestBasicTests.scala     | 1164 +++++++
 .../test/scala/system/basic/WskRestRuleTests.scala |   80 +
 .../src/test/scala/system/basic/WskRuleTests.scala |  393 +++
 .../test/scala/system/basic/WskSequenceTests.scala |  656 ++++
 .../test/scala/system/basic/WskUnicodeTests.scala  |  103 +
 .../test/scala/system/rest/ActionSchemaTests.scala |  118 +
 .../test/scala/system/rest/GoCLINginxTests.scala   |   82 +
 .../src/test/scala/system/rest/JsonSchema.scala    |   42 +
 .../test/scala/system/rest/JsonSchemaTests.scala   |   65 +
 .../src/test/scala/system/rest/RestUtil.scala      |   98 +
 .../test/scala/system/rest/SwaggerTests.scala.off  |   69 +
 openwhisk/tools/actionProxy/Dockerfile             |   19 +
 openwhisk/tools/actionProxy/README.md              |   56 +
 openwhisk/tools/actionProxy/build.gradle           |   19 +
 openwhisk/tools/actionProxy/invoke.py              |  153 +
 openwhisk/tools/admin/README-NEXT.md               |  141 +
 openwhisk/tools/admin/README.md                    |  105 +
 openwhisk/tools/admin/build.gradle                 |   48 +
 .../admin/src/main/resources/application.conf      |   23 +
 .../openwhisk/core/cli/CommandMessages.scala       |   45 +
 .../scala/org/apache/openwhisk/core/cli/Main.scala |  196 ++
 .../openwhisk/core/database/LimitsCommand.scala    |  203 ++
 .../openwhisk/core/database/UserCommand.scala      |  351 +++
 openwhisk/tools/admin/wskadmin                     |  742 +++++
 openwhisk/tools/admin/wskprop.py                   |   82 +
 openwhisk/tools/admin/wskutil.py                   |  106 +
 openwhisk/tools/build/README.md                    |  117 +
 openwhisk/tools/build/checkLogs.py                 |  137 +
 openwhisk/tools/build/citool                       |  347 +++
 openwhisk/tools/build/redo                         |  423 +++
 openwhisk/tools/db/README.md                       |  158 +
 openwhisk/tools/db/cleanUpActivations.py           |   54 +
 openwhisk/tools/db/cosmosDbUtil.py                 |  194 ++
 openwhisk/tools/db/deleteLogsFromActivations.py    |   59 +
 openwhisk/tools/db/moveCodeToAttachment.py         |   95 +
 openwhisk/tools/db/replicateDbs.py                 |  144 +
 openwhisk/tools/dev/README.md                      |  186 ++
 openwhisk/tools/dev/build.gradle                   |   60 +
 .../dev/src/main/groovy/CategoryManager.groovy     |   86 +
 .../tools/dev/src/main/groovy/couchdbViews.groovy  |   59 +
 .../dev/src/main/groovy/intellijRunConfig.groovy   |  259 ++
 .../tools/dev/src/main/groovy/listRepos.groovy     |   99 +
 .../dev/src/main/groovy/renderModuleDetails.groovy |   51 +
 openwhisk/tools/dev/src/main/resources/modules.md  |   37 +
 openwhisk/tools/eclipse/java.xml                   |  295 ++
 openwhisk/tools/eclipse/scala.properties           |   39 +
 openwhisk/tools/git/README.md                      |   37 +
 openwhisk/tools/git/pre-commit-scalafmt-gradlew.sh |   53 +
 openwhisk/tools/git/pre-commit-scalafmt-native.sh  |   66 +
 openwhisk/tools/github/checkAndUploadLogs.sh       |   63 +
 openwhisk/tools/github/debugAction.sh              |   69 +
 openwhisk/tools/github/flake8.sh                   |   42 +
 openwhisk/tools/github/runDummyKOTests.sh          |   26 +
 openwhisk/tools/github/runDummyTests.sh            |   26 +
 openwhisk/tools/github/runLeanSystemTests.sh       |   36 +
 openwhisk/tools/github/runMultiRuntimeTests.sh     |   36 +
 openwhisk/tools/github/runSchedulerTests.sh        |   35 +
 openwhisk/tools/github/runStandaloneTests.sh       |   64 +
 openwhisk/tools/github/runSystemTests.sh           |   36 +
 openwhisk/tools/github/runUnitTests.sh             |   34 +
 openwhisk/tools/github/s3-upload.sh                |   53 +
 openwhisk/tools/github/scan.sh                     |   52 +
 openwhisk/tools/github/setup.sh                    |   84 +
 openwhisk/tools/github/waitIfDebug.sh              |   37 +
 openwhisk/tools/github/writeOnSlack.sh             |   28 +
 openwhisk/tools/jenkins/apache/dockerhub.groovy    |   56 +
 openwhisk/tools/macos/README.md                    |  117 +
 openwhisk/tools/macos/docker-machine/README.md     |  181 ++
 .../tools/macos/docker-machine/tweak-dockerhost.sh |   31 +
 .../macos/docker-machine/tweak-dockermachine.sh    |   67 +
 openwhisk/tools/ow-utils/Dockerfile                |   74 +
 openwhisk/tools/ow-utils/README.md                 |   28 +
 openwhisk/tools/ow-utils/build.gradle              |   41 +
 openwhisk/tools/owperf/README.md                   |  101 +
 openwhisk/tools/owperf/owperf.js                   |  865 ++++++
 openwhisk/tools/owperf/owperf.sh                   |   22 +
 openwhisk/tools/owperf/owperf_data.odg             |  Bin 0 -> 17721 bytes
 openwhisk/tools/owperf/owperf_data.png             |  Bin 0 -> 171488 bytes
 openwhisk/tools/owperf/package.json                |   24 +
 openwhisk/tools/owperf/setup.sh                    |   81 +
 openwhisk/tools/owperf/testAction.js               |   41 +
 openwhisk/tools/travis/README.md                   |   36 +
 openwhisk/tools/travis/box-upload.py               |   89 +
 openwhisk/tools/travis/checkAndUploadLogs.sh       |   48 +
 openwhisk/tools/travis/distDocker.sh               |   36 +
 openwhisk/tools/travis/docker.conf                 |   19 +
 openwhisk/tools/travis/docker.sh                   |   56 +
 openwhisk/tools/travis/flake8.sh                   |   42 +
 openwhisk/tools/travis/runLeanSystemTests.sh       |   36 +
 openwhisk/tools/travis/runMultiRuntimeTests.sh     |   36 +
 openwhisk/tools/travis/runSchedulerTests.sh        |   35 +
 openwhisk/tools/travis/runStandaloneTests.sh       |   64 +
 openwhisk/tools/travis/runSystemTests.sh           |   36 +
 openwhisk/tools/travis/runTests.sh                 |   32 +
 openwhisk/tools/travis/runUnitTests.sh             |   38 +
 openwhisk/tools/travis/scan.sh                     |   52 +
 openwhisk/tools/travis/setup-docker.py             |   78 +
 openwhisk/tools/travis/setup.sh                    |   53 +
 openwhisk/tools/travis/setupLeanSystem.sh          |   33 +
 openwhisk/tools/travis/setupPrereq.sh              |   38 +
 openwhisk/tools/travis/setupSystem.sh              |   36 +
 openwhisk/tools/ubuntu-setup/README.md             |   83 +
 openwhisk/tools/ubuntu-setup/all.sh                |   63 +
 openwhisk/tools/ubuntu-setup/ansible.sh            |   35 +
 openwhisk/tools/ubuntu-setup/bashprofile.sh        |   22 +
 openwhisk/tools/ubuntu-setup/docker-xenial.sh      |   54 +
 openwhisk/tools/ubuntu-setup/docker.sh             |   45 +
 openwhisk/tools/ubuntu-setup/java8.sh              |   39 +
 openwhisk/tools/ubuntu-setup/misc.sh               |   31 +
 openwhisk/tools/ubuntu-setup/pip.sh                |   24 +
 1120 files changed, 174669 insertions(+), 7 deletions(-)

diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 2ea4ad2..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "openwhisk"]
-	path = openwhisk
-	url = https://github.com/nuvolaris/openwhisk.git
diff --git a/Taskfile.yml b/Taskfile.yml
index c8eb9e5..470e9d4 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -80,9 +80,9 @@ tasks:
     cmds:
       - |
         if ! test -d openwhisk/common/scala; then
-          echo "ERROR: openwhisk submodule is not initialized."
-          echo "Run from the OpenServerless root: git submodule update --init --recursive build"
-          echo "Or, from this build checkout: git submodule update --init --recursive openwhisk"
+          echo "ERROR: openwhisk directory is missing or incomplete."
+          echo "This branch vendors openwhisk as a regular directory, not as a submodule."
+          echo "Refresh the build checkout and make sure the build-local branch includes openwhisk/common/scala."
           exit 1
         fi
 
diff --git a/openwhisk b/openwhisk
deleted file mode 160000
index f2cba90..0000000
--- a/openwhisk
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f2cba9062f019c38d367173a19adf7de5b6988c9
diff --git a/openwhisk/.asf.yaml b/openwhisk/.asf.yaml
new file mode 100644
index 0000000..f486d95
--- /dev/null
+++ b/openwhisk/.asf.yaml
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+github:
+  description: "Apache OpenWhisk is an open source serverless cloud platform"
+  homepage: https://openwhisk.apache.org/
+  labels:
+    - openwhisk
+    - apache
+    - serverless
+    - faas
+    - functions-as-a-service
+    - cloud
+    - serverless-architectures
+    - serverless-functions
+    - docker
+    - kubernetes
+    - functions
+  protected_branches:
+    master:
+      required_status_checks:
+        strict: false
+      required_pull_request_reviews:
+        required_appoving_review_count: 1
+      required_signatures: false
+  enabled_merge_buttons:
+    merge: false
+    squash: true
+    rebase: true
+  features:
+    issues: true
+
+notifications:
+  commits: commits@openwhisk.apache.org
+  issues_status: issues@openwhisk.apache.org
+  issues_comment: issues@openwhisk.apache.org
+  pullrequests_status: issues@openwhisk.apache.org
+  pullrequests_comment: issues@openwhisk.apache.org
diff --git a/openwhisk/.gitattributes b/openwhisk/.gitattributes
new file mode 100644
index 0000000..be585a8
--- /dev/null
+++ b/openwhisk/.gitattributes
@@ -0,0 +1,33 @@
+# Auto detect text files and perform LF normalization.
+# Resources:
+#       - https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
+#       - http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
+#       - https://help.github.com/articles/dealing-with-line-endings/
+* text=auto
+
+*.go            text eol=lf
+*.java          text
+*.js            text
+*.md            text
+*.py            text eol=lf
+*.scala         text
+*.sh            text eol=lf
+*.gradle        text
+*.xml           text
+*.bat           text eol=crlf
+
+*.jar           binary
+*.png           binary
+
+# python files not having the .py extension
+tools/cli/wsk      text eol=lf
+tools/cli/wskadmin text eol=lf
+
+# bash files not having the .sh extension
+gradlew                         text eol=lf
+core/javaAction/proxy/gradlew   text eol=lf
+sdk/docker/client/action        text eol=lf
+
+# auth files with default api keys
+ansible/files/auth.guest         text eol=lf
+ansible/files/auth.whisk.system  text eol=lf
diff --git a/openwhisk/.github/ISSUE_TEMPLATE.md b/openwhisk/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..6fbdd42
--- /dev/null
+++ b/openwhisk/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,52 @@
+<!--
+We use the issue tracker for bugs and feature requests. For general questions and discussion please use https://openwhisk.apache.org/slack.html or https://openwhisk.apache.org/community.html instead.
+
+Do NOT share passwords, credentials or other confidential information.
+
+Before creating a new issue, please check if there is one already open that
+fits the defect you are reporting.
+If you open an issue and realize later it is a duplicate of a pre-existing
+open issue, please close yours and add a comment to the other.
+
+Issues can be created for either defects or enhancement requests. If you are a committer than please add the labels "bug" or "feature". If you are not a committer please make clear in the comments which one it is, so that committers can add these labels later.
+
+If you are reporting a defect, please edit the issue description to include the
+information shown below.
+
+If you are reporting an enhancement request, please include information on what you are trying to achieve and why that enhancement would help you.
+
+For more information about reporting issues, see
+https://github.com/apache/openwhisk/blob/master/CONTRIBUTING.md#raising-issues
+
+Use the commands below to provide key information from your environment:
+You do not have to include this information if this is a feature request.
+-->
+
+## Environment details:
+
+* local deployment, native ubuntu, Mac OS, Kubernetes, ...
+* version of docker, ubuntu, ...
+
+## Steps to reproduce the issue:
+
+1.   
+2.   
+3.   
+
+
+## Provide the expected results and outputs:
+
+```
+output comes here
+```
+
+
+## Provide the actual results and outputs:
+
+```
+output comes here
+```
+
+## Additional information you deem important:
+* issue happens only occasionally or under certain circumstances   
+* changes you did or observed in the environment
diff --git a/openwhisk/.github/ISSUE_TEMPLATE/proposal.md b/openwhisk/.github/ISSUE_TEMPLATE/proposal.md
new file mode 100644
index 0000000..51906ab
--- /dev/null
+++ b/openwhisk/.github/ISSUE_TEMPLATE/proposal.md
@@ -0,0 +1,7 @@
+## Enhancement Description
+Please summarize your proposal (e.g. the "what", the "why", and some of the "how").
+
+* Proposal: [a link to the corresponding pull request if it exists already]()
+
+## References
+Please provide links to prior discussion (e.g., Apache `dev` list), wikis, or related issues.
diff --git a/openwhisk/.github/PULL_REQUEST_TEMPLATE.md b/openwhisk/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..1d45d8f
--- /dev/null
+++ b/openwhisk/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,43 @@
+
+<!--- Provide a concise summary of your changes in the Title -->
+
+## Description
+<!--- Provide a detailed description of your changes. -->
+<!--- Include details of what problem you are solving and how your changes are tested. -->
+
+## Related issue and scope
+<!--- Please include a link to a related issue if there is one. -->
+- [ ] I opened an issue to propose and discuss this change (#????)
+
+## My changes affect the following components
+<!--- Select below all system components are affected by your change. -->
+<!--- Enter an `x` in all applicable boxes. -->
+- [ ] API
+- [ ] Controller
+- [ ] Message Bus (e.g., Kafka)
+- [ ] Loadbalancer
+- [ ] Scheduler
+- [ ] Invoker
+- [ ] Intrinsic actions (e.g., sequences, conductors)
+- [ ] Data stores (e.g., CouchDB)
+- [ ] Tests
+- [ ] Deployment
+- [ ] CLI
+- [ ] General tooling
+- [ ] Documentation
+
+## Types of changes
+<!--- What types of changes does your code introduce? Use `x` in all the boxes that apply: -->
+- [ ] Bug fix (generally a non-breaking change which closes an issue).
+- [ ] Enhancement or new feature (adds new functionality).
+- [ ] Breaking change (a bug fix or enhancement which changes existing behavior).
+
+## Checklist:
+<!--- Please review the points below which help you make sure you've covered all aspects of the change you're making. -->
+
+- [ ] I signed an [Apache CLA](https://github.com/apache/openwhisk/blob/master/CONTRIBUTING.md).
+- [ ] I reviewed the [style guides](https://github.com/apache/openwhisk/blob/master/CONTRIBUTING.md#coding-standards) and followed the recommendations (Travis CI will check :).
+- [ ] I added tests to cover my changes.
+- [ ] My changes require further changes to the documentation.
+- [ ] I updated the documentation where necessary.
+
diff --git a/openwhisk/.github/workflows/0-on-demand.yaml b/openwhisk/.github/workflows/0-on-demand.yaml
new file mode 100644
index 0000000..a39a379
--- /dev/null
+++ b/openwhisk/.github/workflows/0-on-demand.yaml
@@ -0,0 +1,92 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: On Demand Tests
+
+on:
+  workflow_dispatch:
+    inputs:
+      enable_ngrok_debug:
+        description: "Enable Ngrok Debugging"
+        required: true
+        type: boolean
+        default: false
+      test_suite:
+        description: Select Test Suite to run
+        type: choice
+        options:
+        - Unit
+        - System
+        - MultiRuntime
+        - Standalone
+        - Scheduler
+        - Performance
+        - Dummy
+
+env:
+  # openwhisk env
+  TEST_SUITE: ${{ inputs.test_suite }}
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+  # (optional) you need to add as secrets an ngrok token and a password to debug a build on demand
+  NGROK_DEBUG: ${{ inputs.enable_ngrok_debug }}
+  NGROK_TOKEN: ${{ secrets.NGROK_TOKEN }}
+  NGROK_PASSWORD: ${{ secrets.NGROK_PASSWORD }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - id: tests
+        name: Run Tests
+        run: "./tools/github/run${{ env.TEST_SUITE }}Tests.sh"
+        continue-on-error: true
+      - id: logs
+        name: Show results and Upload logs
+        run: ./tools/github/checkAndUploadLogs.sh ${{ env.TEST_SUITE }}
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nlogs:' ${{ steps.logs.outputs.logs }}
+             $'\nreport:' ${{ steps.logs.outputs.report }}
+      - name: Debug Action (if requested)
+        run:  ./tools/github/debugAction.sh
+      - name: Wait for Debug (if requested)
+        run: ./tools/github/waitIfDebug.sh
+      - name: Results
+        run: test "${{ steps.tests.outcome }}" ==  "success"
diff --git a/openwhisk/.github/workflows/1-unit.yaml b/openwhisk/.github/workflows/1-unit.yaml
new file mode 100644
index 0000000..27ce75f
--- /dev/null
+++ b/openwhisk/.github/workflows/1-unit.yaml
@@ -0,0 +1,79 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: Unit Tests
+
+on:
+  # build on push
+  push:
+    tags:
+     - '*unit*'
+     - '*all*'
+    branches-ignore: '*'
+  # build on pull requests
+  pull_request:
+
+env:
+  # openwhisk env
+  TEST_SUITE: Unit
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  ## secrets
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # some tests need also this even if they are empty on pull_requests...
+  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+  AWS_REGION: ${{ secrets.AWS_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - id: tests
+        name: Run Tests
+        run: "./tools/github/run${{ env.TEST_SUITE }}Tests.sh"
+        continue-on-error: true
+      - id: logs
+        name: Show results and Upload logs
+        run: ./tools/github/checkAndUploadLogs.sh ${{ env.TEST_SUITE }}
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nlogs:' ${{ steps.logs.outputs.logs }}
+             $'\nreport:' ${{ steps.logs.outputs.report }}
+      - name: Results
+        run: test "${{ steps.tests.outcome }}" ==  "success"
diff --git a/openwhisk/.github/workflows/2-system.yaml b/openwhisk/.github/workflows/2-system.yaml
new file mode 100644
index 0000000..ceb285e
--- /dev/null
+++ b/openwhisk/.github/workflows/2-system.yaml
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: System Tests
+
+on:
+  # build on push
+  push:
+    tags:
+     - '*sys*'
+     - '*all*'
+    branches-ignore: '*'
+  # build on pull requests
+  pull_request:
+
+env:
+  # openwhisk env
+  TEST_SUITE: System
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  ## secrets
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - id: tests
+        name: Run Tests
+        run: "./tools/github/run${{ env.TEST_SUITE }}Tests.sh"
+        continue-on-error: true
+      - id: logs
+        name: Show results and Upload logs
+        run: ./tools/github/checkAndUploadLogs.sh ${{ env.TEST_SUITE }}
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nlogs:' ${{ steps.logs.outputs.logs }}
+             $'\nreport:' ${{ steps.logs.outputs.report }}
+      - name: Results
+        run: test "${{ steps.tests.outcome }}" ==  "success"
diff --git a/openwhisk/.github/workflows/3-multi-runtime.yaml b/openwhisk/.github/workflows/3-multi-runtime.yaml
new file mode 100644
index 0000000..4544d28
--- /dev/null
+++ b/openwhisk/.github/workflows/3-multi-runtime.yaml
@@ -0,0 +1,75 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: MultiRuntime Tests
+
+on:
+  # build on push
+  push:
+    tags:
+     - '*multi*'
+     - '*all*'
+
+    branches-ignore: '*'
+  # build on pull requests
+  pull_request:
+
+env:
+  # openwhisk env
+  TEST_SUITE: MultiRuntime
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  ## secrets
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - id: tests
+        name: Run Tests
+        run: "./tools/github/run${{ env.TEST_SUITE }}Tests.sh"
+        continue-on-error: true
+      - id: logs
+        name: Show results and Upload logs
+        run: ./tools/github/checkAndUploadLogs.sh ${{ env.TEST_SUITE }}
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nlogs:' ${{ steps.logs.outputs.logs }}
+             $'\nreport:' ${{ steps.logs.outputs.report }}
+      - name: Results
+        run: test "${{ steps.tests.outcome }}" ==  "success"
diff --git a/openwhisk/.github/workflows/4-standalone.yaml b/openwhisk/.github/workflows/4-standalone.yaml
new file mode 100644
index 0000000..35be409
--- /dev/null
+++ b/openwhisk/.github/workflows/4-standalone.yaml
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: Standalone Tests
+
+on:
+  # build on push
+  push:
+    tags:
+     - '*stand*'
+     - '*all*'
+    branches-ignore: '*'
+  # build on pull requests
+  pull_request:
+
+env:
+  # openwhisk env
+  TEST_SUITE: Standalone
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  ## secrets
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - id: tests
+        name: Run Tests
+        run: "./tools/github/run${{ env.TEST_SUITE }}Tests.sh"
+        continue-on-error: true
+      - id: logs
+        name: Show results and Upload logs
+        run: ./tools/github/checkAndUploadLogs.sh ${{ env.TEST_SUITE }}
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nlogs:' ${{ steps.logs.outputs.logs }}
+             $'\nreport:' ${{ steps.logs.outputs.report }}
+      - name: Results
+        run: test "${{ steps.tests.outcome }}" ==  "success"
diff --git a/openwhisk/.github/workflows/5-scheduler.yaml b/openwhisk/.github/workflows/5-scheduler.yaml
new file mode 100644
index 0000000..cafada4
--- /dev/null
+++ b/openwhisk/.github/workflows/5-scheduler.yaml
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: Scheduler Tests
+
+on:
+  # build on push
+  push:
+    tags:
+      - '*sched*'
+      - '*all*'
+    branches-ignore: '*'
+  # build on pull requests
+  pull_request:
+
+env:
+  # openwhisk env
+  TEST_SUITE: Scheduler
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  ## secrets
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - id: tests
+        name: Run Tests
+        run: "./tools/github/run${{ env.TEST_SUITE }}Tests.sh"
+        continue-on-error: true
+      - id: logs
+        name: Show results and Upload logs
+        run: ./tools/github/checkAndUploadLogs.sh ${{ env.TEST_SUITE }}
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nlogs:' ${{ steps.logs.outputs.logs }}
+             $'\nreport:' ${{ steps.logs.outputs.report }}
+      - name: Results
+        run: test "${{ steps.tests.outcome }}" ==  "success"
diff --git a/openwhisk/.github/workflows/6-performance.yaml b/openwhisk/.github/workflows/6-performance.yaml
new file mode 100644
index 0000000..4cdb6a9
--- /dev/null
+++ b/openwhisk/.github/workflows/6-performance.yaml
@@ -0,0 +1,88 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+name: Performance Tests
+
+on:
+  # build on push
+  push:
+    tags:
+     - '*perf*'
+     - '*all*'
+    branches-ignore: '*'
+  # build on pull requests
+  pull_request:
+
+env:
+  # openwhisk env
+  TEST_SUITE: Performance
+  ANSIBLE_CMD: "ansible-playbook -i environments/local -e docker_image_prefix=testing"
+  GRADLE_PROJS_SKIP: ""
+
+  ## secrets
+  # (optional) slack incoming wehbook for notifications
+  SLACK_WEBHOOK: ${{secrets.SLACK_WEBHOOK}}
+
+  # (optional) s3 log upload
+  LOG_BUCKET: ${{ secrets.LOG_BUCKET }}
+  LOG_ACCESS_KEY_ID: ${{ secrets.LOG_ACCESS_KEY_ID }}
+  LOG_SECRET_ACCESS_KEY: ${{ secrets.LOG_SECRET_ACCESS_KEY }}
+  LOG_REGION: ${{ secrets.LOG_REGION }}
+
+  # github
+  GH_BUILD: ${{ github.event_name }}-${{ github.sha }}
+  GH_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+  openwhisk:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: "Setup"
+        run: ./tools/github/setup.sh
+      - run: ./tests/performance/preparation/deploy.sh
+      - run: TERM=dumb ./tests/performance/wrk_tests/latency.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/noop.js 2m
+        continue-on-error: true
+      - run: TERM=dumb ./tests/performance/wrk_tests/latency.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/async.js 2m
+        continue-on-error: true
+      - run: TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/noop.js 4 1 2 2m
+        continue-on-error: true
+      - run: TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/async.js 4 1 2 2m
+        continue-on-error: true
+      - run: TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/noop.js 100 110 2 2m
+        continue-on-error: true
+      - run: TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/async.js 100 110 2 2m
+        continue-on-error: true
+      - run: OPENWHISK_HOST="172.17.0.1" CONNECTIONS="100" REQUESTS_PER_SEC="1" ./gradlew gatlingRun-org.apache.openwhisk.ApiV1Simulation
+        continue-on-error: true
+      - run: OPENWHISK_HOST="172.17.0.1" MEAN_RESPONSE_TIME="1000" API_KEY="$(cat ansible/files/auth.guest)" EXCLUDED_KINDS="python:default,java:default,swift:default" PAUSE_BETWEEN_INVOKES="100" ./gradlew gatlingRun-org.apache.openwhisk.LatencySimulation
+        continue-on-error: true
+      - run: OPENWHISK_HOST="172.17.0.1" API_KEY="$(cat ansible/files/auth.guest)" CONNECTIONS="100" REQUESTS_PER_SEC="1" ./gradlew gatlingRun-org.apache.openwhisk.BlockingInvokeOneActionSimulation
+        continue-on-error: true
+      - run: OPENWHISK_HOST="172.17.0.1" API_KEY="$(cat ansible/files/auth.guest)" CONNECTIONS="100" REQUESTS_PER_SEC="1" ASYNC="true" ./gradlew gatlingRun-org.apache.openwhisk.BlockingInvokeOneActionSimulation
+        continue-on-error: true
+      # The following configuration does not make much sense. But we do not have enough users. But it's good to verify, that the test is still working.
+      - run: OPENWHISK_HOST="172.17.0.1" USERS="1" REQUESTS_PER_SEC="1" ./gradlew gatlingRun-org.apache.openwhisk.ColdBlockingInvokeSimulation
+        continue-on-error: true
+      - name: Slack Notification
+        run: >
+             ./tools/github/writeOnSlack.sh
+             "[$TEST_SUITE]" ${{ steps.tests.outcome }} on ${GH_BUILD}
+             $'\nbranch:' $GH_BRANCH
+             $'\nmessage:' "$(git log -1 --oneline | cat)"
+             $'\nCheck GitHub logs for results'
diff --git a/openwhisk/.github/workflows/README.md b/openwhisk/.github/workflows/README.md
new file mode 100644
index 0000000..dbe6c70
--- /dev/null
+++ b/openwhisk/.github/workflows/README.md
@@ -0,0 +1,56 @@
+# How to use those workflows
+
+There are a few [GitHub secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) to configure to fully leverage the build.
+
+You can use and set the followings secrets also in your fork.
+
+## Ngrok Debugging
+
+You can debug a GitHub Action build using [NGROK](https://ngrok.com/).
+
+It is disabled for automated build triggered by push and pull_requests.
+
+You can trigger a workflow run manually  enabling ngrok debugging.
+
+It will open an ssh connection to the VM and keep it up and running for one hour.
+The connection url is showns in the log for debugAction.sh
+
+You can then connect to the build vm, and debug it.
+You need to use a password of your choice to access it.
+
+You can continue the build with `touch /tmp/continue`.
+You can abort the build with `touch /tmp/abort`.
+
+To enable this option you have to register to Ngrok, using the fee account and get the NGROK Token.
+
+Then set the following secrets:
+
+- `NGROK_TOKEN` to the ngrok token.
+- `NGROK_PASSWORD` to a password of choice to access the build with the ssh command generated.
+
+## Log Upload
+
+The build uploads the logs to an s3 bucket allowing to inspect them with a browser.
+
+You need to create the bucket with the following commands:
+
+```
+LOG_BUCKET=<name-of-your-bucket>
+LOG_REGION=<the-region-you-use>
+aws s3 mb s3://$LOG_BUCKET --region $LOG_REGION
+aws s3 website s3://$LOG_BUCKET/ --index-document index.html
+aws s3api put-bucket-acl --acl public-read --bucket $LOG_BUCKET
+```
+
+To enable upload to the created bucket you need to set the following secrets:
+
+- `LOG_BUCKET`: name of your bucket in s3 (just the name, without `s3://`); create it before.
+- `LOG_ACCESS_KEY_ID`: your aws access key.
+- `LOG_SECRET_ACCESS_KEY`: your aws secret key.
+- `LOG_REGION`: important: the region where your bucket is.
+
+## Slack notification
+
+If you want to get notified of what happens on slack, create an [Incoming Web Hook](https://api.slack.com/messaging/webhooks) and then set the following secret:
+
+- `SLACK_WEBHOOK`: the incoming webhook url provided by slack.
diff --git a/openwhisk/.gitignore b/openwhisk/.gitignore
new file mode 100644
index 0000000..7f17127
--- /dev/null
+++ b/openwhisk/.gitignore
@@ -0,0 +1,104 @@
+# Whisk
+nginx.conf
+whisk.properties
+whisk.conf
+default.props
+/tests/src/test/resources/application.conf
+
+.ant-targets-build.xml
+/results/
+/logs/
+/config/custom-config.xml
+results
+*.retry
+
+# Environments
+/ansible/environments/*
+!/ansible/environments/distributed
+!/ansible/environments/docker-machine
+!/ansible/environments/local
+!/ansible/environments/mac
+
+# Eclipse
+bin/
+**/.project
+.settings/
+.classpath
+.cache-main
+.cache-tests
+
+# Linux
+*~
+*.swp
+
+# Mac
+.DS_Store
+
+# Gradle
+.gradle
+build/
+!/tools/build/
+
+# Python
+.ipynb_checkpoints/
+*.pyc
+
+# NodeJS
+node_modules
+
+# IntelliJ
+.idea
+*.class
+*.iml
+out/
+
+# Ansible
+ansible/environments/docker-machine/hosts
+ansible/environments/local/hosts
+ansible/db_local.ini*
+ansible/tmp/*
+ansible/roles/nginx/files/openwhisk-client*
+ansible/roles/nginx/files/*.csr
+ansible/roles/nginx/files/*.p12
+ansible/roles/nginx/files/*cert.pem
+ansible/roles/nginx/files/*p12
+ansible/roles/kafka/files/*
+ansible/roles/controller/files/*
+ansible/roles/invoker/files/*
+
+# .zip files must be explicited whitelisted
+*.zip
+tests/dat/actions/sleep.jar
+tests/dat/actions/blackbox.zip
+tests/dat/actions/python.zip
+tests/dat/actions/zippedaction.zip
+tests/dat/actions/zippedaction/node_modules
+tests/dat/actions/python2_virtualenv.zip
+tests/dat/actions/python_virtualenv/virtualenv
+tests/dat/actions/.built
+tests/dat/actions/unicode.tests/java-8.bin
+tests/performance/gatling_tests/src/gatling/resources/data/javaAction.jar
+tests/performance/gatling_tests/.built
+
+# dev
+intellij-run-config.groovy
+
+# VSCode
+.vscode/
+
+# route management
+core/routemgmt/createApi/apigw-utils.js
+core/routemgmt/createApi/package-lock.json
+core/routemgmt/createApi/utils.js
+core/routemgmt/deleteApi/apigw-utils.js
+core/routemgmt/deleteApi/package-lock.json
+core/routemgmt/deleteApi/utils.js
+core/routemgmt/getApi/apigw-utils.js
+core/routemgmt/getApi/package-lock.json
+core/routemgmt/getApi/utils.js
+
+# vscode metals
+.bloop/
+.metals/
+.git-hooks/
+*.tar
diff --git a/openwhisk/.pydevproject b/openwhisk/.pydevproject
new file mode 100644
index 0000000..a49a94d
--- /dev/null
+++ b/openwhisk/.pydevproject
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?><pydev_project>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 3.6</pydev_property>
+</pydev_project>
diff --git a/openwhisk/.scalafmt.conf b/openwhisk/.scalafmt.conf
new file mode 100644
index 0000000..cc0159a
--- /dev/null
+++ b/openwhisk/.scalafmt.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+style = intellij
+danglingParentheses = false
+maxColumn = 120
+docstrings = JavaDoc
+rewrite.rules = [SortImports]
+project.git = true
diff --git a/openwhisk/.travis.yml b/openwhisk/.travis.yml
new file mode 100644
index 0000000..a50894d
--- /dev/null
+++ b/openwhisk/.travis.yml
@@ -0,0 +1,106 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+os: linux
+dist: focal
+language: java
+jdk: openjdk11
+
+services:
+  - docker
+
+env:
+  global:
+    - ANSIBLE_CMD="ansible-playbook -i environments/local -e docker_image_prefix=testing"
+    - GRADLE_PROJS_SKIP=""
+
+notifications:
+  email: false
+  slack:
+    rooms:
+      - secure: "Rrj5ya7JV7bfgW3GXZpuRsX2e9/qsTpLLVZAzgq2Z3xW/IVAqeAM8DDR0tjPbR7Nou0RpPtE/ynkps4Qd6CGTBiCULpcGi2cXJ+p35WvaweWlFIKU0Vam9iEWXQ+bG1LwB61LrSmD7DJvOxkZiwtagqUfYLhVeD1WfgcWc/lj0F0julCScujqqJbj/jUnlkSm6VJz9m11NQkvZZ+YFgNYCPAQRmSh59K0ELximJ54C4/af05YHq6QLWUh/2Eoa68p3XzQyONtteMRvbAJgmMGIijQsKa+ZELrZPjz/5EplMA+FEUjoW6tn3NQjQgZOdDHq3fEbnuit0OOBMRu5QhYT0MjV6QJANYmJU2QqVoDubgTtQSeHJNYoM1fI15yfuSCJg+H6FnI0ncXRB3zz7EZ3zksKKeWZ20NQVKBNCYRNIBC8Im3ff8TbZdulH/Qk/NEeOJwpgm8yfUqUqLu3dt13KT0Jd [...]
+      - secure: "WgdL9/Z/9+rl6edh+mqDiO0mx8JpxYwIrudYwuZUZ7d/9oUzS8Miid7au7Y1PP0CVPVDEvastw6+NnIVA/BIHTBNj488dTeNXumaaNQlaTTvaI9OLN1w3+GVVFuF64gmJ6I2T6x+qxqlYOwQhyZ8jZEJUXQ3VwE6DPz+KNqpBy+6GgWEvi+2lgGhc2Ko3SPAtcDWAdE6t8ELaO2uLh/JU60ndAi4yT297fOfwWi4I1aee5a+LajbJN6Mz89o5o0Y2GHV83a3D/ablt1CH3kRSErqLV3HDbt5eRR4yFhTPpbZG8yeWcZkP0oKyt3QqSen1paiEeqr7R9OspDf2OLSlWx7TGm/BnNW/3YRI3+gUPHqaJPCLldNRWnbYYd0RFGjVOfVOoYhAWAMdkWT48jX3YGE1LaZ0SwlxpMy5Fpj8Q22SVXY51o5Pw1UomUhGz2Hjfj8b0WbkWQ+q5qpgR8/z4Zfz5i [...]
+    on_pull_requests: false
+    on_success: change
+    on_failure: always
+  webhooks:
+    urls:
+      # travis2slack webhook to enable DMs on openwhisk-team.slack.com to PR authors with TravisCI results
+      secure: "qSkFNkMDYhp025vW+hBWO4wlMEeaz2S2c3tsZgIhcqji58AAxL2KKuNIWAuzftIxC5l5RwPt/OBXWFjgnAwCjj3hfczMfpuexGPruzmRe2GYbBwS0OhwDkWVosl9UqFELbZEdQ/tTJyW7p3Gd4FSjCJBCAdRL5yYcn3RiCj/UrJ6454IkKqeuTihWp2ZklqnZpMv9WzC4DK+cI/rJUP5megh/gilORfBD5/wnMMksC5NSlpc2WAtdeKusVZiZ6ieR6uLoR3rwzgUBC3PbyX8GO1OIh+LnvO7c0Hrhc2u685nxrCMseJIH9kZQboGA7RbiH64U5Zy7dK6fiFGfQJc3YyfsbKBKyd0QlvZp1vKw4iAvtsyentodjR1lN29fuTUW3hPMCo/YJSxZmHHMRSsoqs4zMp7lHo0aDfBOJRB+jjluA96Tn8SZjIATu76SwzJ0EolyVGw25HqKxSX0eM0ZLgrVrh89 [...]
+
+# specific cache configuration for gradle based builds
+# see: https://docs.travis-ci.com/user/languages/java/#caching
+before_cache:
+  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock
+  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
+
+cache:
+  directories:
+    - $HOME/.gradle/caches/
+    - $HOME/.gradle/wrapper/
+
+before_install:
+  - sudo systemctl stop epmd.socket epmd.service
+  - ./tools/travis/docker.sh
+  - sudo apt-get install -y python3-pip
+  - pip install --user --upgrade pip setuptools six
+  - pip3 install --user --upgrade pip setuptools six
+
+install:
+  - ./tools/travis/setup.sh
+
+jobs:
+  include:
+    - script:
+        - TERM=dumb OW_SCALA_VERSION=2.13 ./gradlew :tests:compileTestScala
+      name: "Scala 2.13 compilation"
+    # - script:
+    #     - ./tools/travis/runUnitTests.sh
+    #     - ./tools/travis/checkAndUploadLogs.sh unit db
+    #   name: "Unit Tests"
+    # - script:
+    #   - ./tools/travis/runSystemTests.sh
+    #   - ./tools/travis/checkAndUploadLogs.sh system
+    #   name: "System Tests"
+    # - script:
+    #   - ./tools/travis/runMultiRuntimeTests.sh
+    #   - ./tools/travis/checkAndUploadLogs.sh multi-runtime
+    #   name: "Multi-Runtime Tests"
+    # - script:
+    #     - ./tools/travis/runStandaloneTests.sh
+    #     - ./tools/travis/checkAndUploadLogs.sh standalone
+    #   name: "Standalone Tests"
+    # - script:
+    #     - ./tests/performance/preparation/deploy.sh
+    #     - TERM=dumb ./tests/performance/wrk_tests/latency.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/noop.js 2m
+    #     - TERM=dumb ./tests/performance/wrk_tests/latency.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/async.js 2m
+    #     - TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/noop.js 4 1 2 2m
+    #     - TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/async.js 4 1 2 2m
+    #     - TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/noop.js 100 110 2 2m
+    #     - TERM=dumb ./tests/performance/wrk_tests/throughput.sh "https://172.17.0.1:10001" "$(cat ansible/files/auth.guest)" ./tests/performance/preparation/actions/async.js 100 110 2 2m
+    #     - OPENWHISK_HOST="172.17.0.1" CONNECTIONS="100" REQUESTS_PER_SEC="1" ./gradlew gatlingRun-org.apache.openwhisk.ApiV1Simulation
+    #     - OPENWHISK_HOST="172.17.0.1" MEAN_RESPONSE_TIME="1000" API_KEY="$(cat ansible/files/auth.guest)" EXCLUDED_KINDS="python:default,java:default,swift:default" PAUSE_BETWEEN_INVOKES="100" ./gradlew gatlingRun-org.apache.openwhisk.LatencySimulation
+    #     - OPENWHISK_HOST="172.17.0.1" API_KEY="$(cat ansible/files/auth.guest)" CONNECTIONS="100" REQUESTS_PER_SEC="1" ./gradlew gatlingRun-org.apache.openwhisk.BlockingInvokeOneActionSimulation
+    #     - OPENWHISK_HOST="172.17.0.1" API_KEY="$(cat ansible/files/auth.guest)" CONNECTIONS="100" REQUESTS_PER_SEC="1" ASYNC="true" ./gradlew gatlingRun-org.apache.openwhisk.BlockingInvokeOneActionSimulation
+    #     # The following configuration does not make much sense. But we do not have enough users. But it's good to verify, that the test is still working.
+    #     - OPENWHISK_HOST="172.17.0.1" USERS="1" REQUESTS_PER_SEC="1" ./gradlew gatlingRun-org.apache.openwhisk.ColdBlockingInvokeSimulation
+    #     - ./tools/travis/checkAndUploadLogs.sh perf
+    #   name: "Performance Tests"
+    # - script:
+    #     - ./tools/travis/runSchedulerTests.sh
+    #     - ./tools/travis/checkAndUploadLogs.sh scheduler
+    #   name: "Scheduler Tests"
diff --git a/openwhisk/CHANGELOG.md b/openwhisk/CHANGELOG.md
new file mode 100644
index 0000000..1e78221
--- /dev/null
+++ b/openwhisk/CHANGELOG.md
@@ -0,0 +1,91 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+# OpenWhisk Core
+
+## Apache 1.0.0
+### Branch: [1.0.0](https://github.com/apache/openwhisk/tree/1.0.0)
+### Notable changes
+- Improvements to parameter encryption to support per-namespace keys. ([#4855](https://github.com/apache/openwhisk/pull/4855), [@rabbah](https://github.com/rabbah))
+- Use latest code if action's revision is mismatched. ([#4954](https://github.com/apache/openwhisk/pull/4954), [@upgle](https://github.com/upgle))
+- Do not delete previous annotation and support delete annotation via CLI. ([#4940](https://github.com/apache/openwhisk/pull/4940), [@ningyougang](https://github.com/ningyougang))
+- Prewarm eviction variance. ([#4916](https://github.com/apache/openwhisk/pull/4916), [@tysonnorris](https://github.com/tysonnorris))
+- Allow to get activation list by a binding package name. ([#4919](https://github.com/apache/openwhisk/pull/4919), [@upgle](https://github.com/upgle))
+- Allow parent/child transaction ids. ([#4819](https://github.com/apache/openwhisk/pull/4819), [@upgle](https://github.com/upgle))
+- Adjust prewarm container dynamically. ([#4871](https://github.com/apache/openwhisk/pull/4871), [@ningyougang](https://github.com/ningyougang))
+- Add NodeJS 14 runtime. ([#4902](https://github.com/apache/openwhisk/pull/4902), [@rabbah](https://github.com/rabbah))
+- Create AES128 and AES256 encryption for parameters. ([#4756](https://github.com/apache/openwhisk/pull/4756), [@mcdan](https://github.com/mcdan))
+- Implement an ElasticSearchActivationStore. ([#4724](https://github.com/apache/openwhisk/pull/4724), [@jiangpengcheng](https://github.com/jiangpengcheng))
+- Add Swift 5.1 runtime to runtimes.json. ([#4803](https://github.com/apache/openwhisk/pull/4803), [@dgrove-oss](https://github.com/dgrove-oss))
+- Add volume mapping for Docker credentials. ([#4791](https://github.com/apache/openwhisk/pull/4791), [@style95](https://github.com/style95))
+- add .NET Core 3.1 runtime kind. ([#4792](https://github.com/apache/openwhisk/pull/4792), [@dgrove-oss](https://github.com/dgrove-oss))
+- Add PHP 7.4 runtime. ([#4767](https://github.com/apache/openwhisk/pull/4767), [@akrabat](https://github.com/akrabat))
+- Serialize `updated` value of entity document in response. ([#4646](https://github.com/apache/openwhisk/pull/4646), [@upgle](https://github.com/upgle))
+- Provide environment at init time. ([#4722](https://github.com/apache/openwhisk/pull/4722), [@upgle](https://github.com/upgle))
+- OpenWhisk User Events. ([#4584](https://github.com/apache/openwhisk/pull/4584), [@selfxp](https://github.com/selfxp))
+- Openwhisk in a standalone runnable jar. ([#4516](https://github.com/apache/openwhisk/pull/4516), [@chetanmeh](https://github.com/chetanmeh))
+- Update Docker client version to 18.06.3. ([#4430](https://github.com/apache/openwhisk/pull/4430), [@style95](https://github.com/style95))
+- Add `binding` annotation to record an action path not resolved. ([#4211](https://github.com/apache/openwhisk/pull/4211), [@upgle](https://github.com/upgle))
+- Add SPI for invoker. ([#4453](https://github.com/apache/openwhisk/pull/4453), [@style95](https://github.com/style95))
+- Enable CouchDB persist_path in a distributed environment as well. ([#4290](https://github.com/apache/openwhisk/pull/4290), [@style95](https://github.com/style95))
+- Feature flag to turn on/off support for provide-api-key annotation. ([#4334](https://github.com/apache/openwhisk/pull/4334), [@chetanmeh](https://github.com/chetanmeh))
+- Add annotations to inject the API key into the action context. ([#4284](https://github.com/apache/openwhisk/pull/4284), [@rabbah](https://github.com/rabbah))
+- Update CosmosDB to 2.4.2. ([#4321](https://github.com/apache/openwhisk/pull/4321), [@chetanmeh](https://github.com/chetanmeh))
+- Adding YARNContainerFactory. ([#4129](https://github.com/apache/openwhisk/pull/4129), [@SamHjelmfelt](https://github.com/SamHjelmfelt))
+- Allow persisted CouchDB directory mount. ([#4250](https://github.com/apache/openwhisk/pull/4250), [@rabbah](https://github.com/rabbah))
+- Bump ephemeral CouchDB to v2.3. ([#4202](https://github.com/apache/openwhisk/pull/4202), [@jonpspri](https://github.com/jonpspri))
+- Add Ballerina 0.990.2 runtime. ([#4239](https://github.com/apache/openwhisk/pull/4239), [@rabbah](https://github.com/rabbah))
+- Add Swift 4.2 runtime in default deployment. ([#4210](https://github.com/apache/openwhisk/pull/4210), [@csantanapr](https://github.com/csantanapr))
+- Add PHP 7.3 runtime. ([#4182](https://github.com/apache/openwhisk/pull/4182), [@akrabat](https://github.com/akrabat))
+- Add .NET Core 2.2 runtime. ([#4172](https://github.com/apache/openwhisk/pull/4172), [@shawnallen85](https://github.com/shawnallen85))
+- Updated Intellij script to start controller and invoker locally. ([#4142](https://github.com/apache/openwhisk/pull/4142), [@ddragosd](https://github.com/ddragosd))
+- Ensure ResultMessage is processed. ([#4135](https://github.com/apache/openwhisk/pull/4135), [@jiangpengcheng](https://github.com/jiangpengcheng))
+- Protect Package Bindings from containing circular references. ([#4122](https://github.com/apache/openwhisk/pull/4122), [@asteed](https://github.com/asteed))
+- Ensure, that Result-ack is sent before Completion-ack. ([#4115](https://github.com/apache/openwhisk/pull/4115), [@cbickel](https://github.com/cbickel))
+- Add NodeJS 10 runtime to default set of runtimes for ansible/vagrant. ([#4124](https://github.com/apache/openwhisk/pull/4124), [@csantanapr](https://github.com/csantanapr))
+- Enable concurrent activation processing. ([#2795](https://github.com/apache/openwhisk/pull/2795), [@tysonnorris](https://github.com/tysonnorris))
+- Rename the package from whisk to org.apache.openwhisk. ([#4073](https://github.com/apache/openwhisk/pull/4073), [@houshengbo](https://github.com/houshengbo))
+- Allow web actions from package bindings. ([#3880](https://github.com/apache/openwhisk/pull/3880), [@upgle](https://github.com/upgle))
+- Switch to Scala 2.12.7 ([#4062](https://github.com/apache/openwhisk/pull/4062), [@chetanmeh](https://github.com/chetanmeh))
+- Always return activation without logs on blocking invoke. ([#4100](https://github.com/apache/openwhisk/pull/4100), [@cbickel](https://github.com/cbickel))
+- Changes to include Go runtime. ([#4093](https://github.com/apache/openwhisk/pull/4093), [@sciabarracom](https://github.com/sciabarracom))
+- Send active-ack after log collection for non-blocking activations. ([#4041](https://github.com/apache/openwhisk/pull/4041), [@cbickel](https://github.com/cbickel))
+- Increase max-content-length to 50 MB. ([#4059](https://github.com/apache/openwhisk/pull/4059), [@chetanmeh](https://github.com/chetanmeh))
+- Using non-root user in controller. ([#3579](https://github.com/apache/openwhisk/pull/3579), [@Himavanth](https://github.com/Himavanth))
+- Customize invoker user memory for memory based load-balancing. ([#4011](https://github.com/apache/openwhisk/pull/4011), [@ningyougang](https://github.com/ningyougang))
+- Secure the invoker with SSL. ([#3968](https://github.com/apache/openwhisk/pull/3968), [@cbickel](https://github.com/cbickel))
+- Reuse a container on `applicationError`. ([#3941](https://github.com/apache/openwhisk/pull/3941), [@tysonnorris](https://github.com/tysonnorris))
+- Memory based load-balancing ([#3747](https://github.com/apache/openwhisk/pull/3747), [@cbickel](https://github.com/cbickel))
+- Activation ID in header. ([#3671](https://github.com/apache/openwhisk/pull/3671), [@style95](https://github.com/style95))
+- Treat action code as attachments. ([#3945](https://github.com/apache/openwhisk/pull/3945), [@chetanmeh](https://github.com/chetanmeh))
+- K8S: Implement invoker-node affinity and eliminate usage of kubectl. ([#3963](https://github.com/apache/openwhisk/pull/3963), [@dgrove-oss](https://github.com/dgrove-oss))
+- Add Ruby 2.5 runtime support. ([#3725](https://github.com/apache/openwhisk/pull/3725), [@remore](https://github.com/remore))
+- S3AttachmentStore. ([#3779](https://github.com/apache/openwhisk/pull/3779), [@chetanmeh](https://github.com/chetanmeh))
+- ContainerClient + Akka HTTP alternative to HttpUtils. ([#3812](https://github.com/apache/openwhisk/pull/3812), [@tysonnorris](https://github.com/tysonnorris))
+- Throttle the system based on active-ack timeouts. ([#3875](https://github.com/apache/openwhisk/pull/3875), [@markusthoemmes](https://github.com/markusthoemmes))
+- Recover image pulls by trying to run the container anyways. ([#3813](https://github.com/apache/openwhisk/pull/3813), [@markusthoemmes](https://github.com/markusthoemmes))
+- Use separate DB users for deployed components. ([#3876](https://github.com/apache/openwhisk/pull/3876), [@cbickel](https://github.com/cbickel))
+- Introduce SPI to swap authentication directives. ([#3829](https://github.com/apache/openwhisk/pull/3829), [@mhenke1](https://github.com/mhenke1))
+- ArtifactStore implementation for CosmosDB. ([#3562](https://github.com/apache/openwhisk/pull/3562), [@chetanmeh](https://github.com/chetanmeh))
+- Add support for PHP 7.2 runtime. ([#3736](https://github.com/apache/openwhisk/pull/3736), [@akrabat](https://github.com/akrabat))
+
+## Incubating 0.9.0
+### Branch: [0.9.0-incubating](https://github.com/apache/openwhisk/tree/0.9.0-incubating)
+### Notable changes
+- Initial release.
diff --git a/openwhisk/CODE_OF_CONDUCT.md b/openwhisk/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..b10afd7
--- /dev/null
+++ b/openwhisk/CODE_OF_CONDUCT.md
@@ -0,0 +1,22 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+# Apache OpenWhisk Community Code of Conduct
+
+Please refer to the [Apache Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
diff --git a/openwhisk/CONTRIBUTING.md b/openwhisk/CONTRIBUTING.md
new file mode 100644
index 0000000..aa9c195
--- /dev/null
+++ b/openwhisk/CONTRIBUTING.md
@@ -0,0 +1,71 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+
+# Contributing to Apache OpenWhisk
+
+Anyone can contribute to the OpenWhisk project and we welcome your contributions.
+
+There are multiple ways to contribute: report bugs, improve the docs, and
+contribute code, but you must follow these prerequisites and guidelines:
+
+ - [Contributor License Agreement](#contributor-license-agreement)
+ - [Raising issues](#raising-issues)
+ - [Coding Standards](#coding-standards)
+
+### Contributor License Agreement
+
+All contributors must sign and submit an Apache CLA (Contributor License Agreement).
+
+Instructions on how to do this can be found here:
+[http://www.apache.org/licenses/#clas](http://www.apache.org/licenses/#clas)
+
+Sign the appropriate CLA and submit it to the Apache Software Foundation (ASF) secretary. You will receive a confirmation email from the ASF and be added to
+the following list: http://people.apache.org/unlistedclas.html.  Once your name is on this list, you are done and your PR can be merged.
+
+Project committers will use this list to verify pull requests (PRs) come from contributors that have signed a CLA.
+
+We look forward to your contributions!
+
+## Raising issues
+
+Please raise any bug reports or enhancement requests on the respective project repository's GitHub issue tracker. Be sure to search the
+list to see if your issue has already been raised.
+
+A good bug report is one that make it easy for us to understand what you were trying to do and what went wrong.
+Provide as much context as possible so we can try to recreate the issue.
+
+A good enhancement request comes with an explanation of what you are trying to do and how that enhancement would help you.
+
+### Discussion
+
+Please use the project's developer email list to engage our community:
+[dev@openwhisk.apache.org](dev@openwhisk.apache.org)
+
+In addition, we provide a "dev" Slack team channel for conversations at:
+https://openwhisk-team.slack.com/messages/dev/
+
+### Coding standards
+
+Please ensure you follow the coding standards used throughout the existing
+code base. Some basic rules include:
+
+ - all files must have the Apache license in the header.
+ - all PRs must have passing builds for all operating systems.
+ - the code is correctly formatted as defined in the [Scalariform plugin properties](tools/eclipse/scala.properties). If you use IntelliJ for development this [page](https://plugins.jetbrains.com/plugin/7480-scalariform) describes the setup and configuration of the plugin.
diff --git a/openwhisk/CREDITS.txt b/openwhisk/CREDITS.txt
new file mode 100644
index 0000000..2313835
--- /dev/null
+++ b/openwhisk/CREDITS.txt
@@ -0,0 +1,90 @@
+                                C R E D I T S
+
+This is the file where major donations to the OpenWhisk project are listed and it
+should be used to give appropriate visibility to those individuals, organizations
+or companies that donated resources to the effort. This file wants to be one of
+the ways the OpenWhisk community pays back.
+
+
+Community Credits
+-----------------
+
+The OpenWhisk project code was originally created, designed, developed (under an
+IBM copyright dated 2015-2016) and donated by the IBM Corporation (http://www.ibm.com/)
+to the Apache Software Foundation (ASF). The following repositories were included in
+the initial donation:
+
+* openwhisk:
+    * Primary (core) source code repository including platform code, run books, tests and more.
+* openwhisk-catalog:
+    * Catalog of built-in system, utility, test and sample Actions, Feeds and provider integration tooling.
+* openwhisk-client-js:
+    * JavaScript (JS) client library for the OpenWhisk platform.
+* openwhisk-client-swift:
+    * Swift-based client SDK for OpenWhisk compatible with Swift 2.x and runs on iOS 9, WatchOS 2, and Darwin.
+* openwhisk-sdk-docker: 
+    * SDK that shows how to create “Black box” Docker containers that can run Action (code).
+* openwhisk-client-go:
+    * API Framework written in GoLang (in-progress)
+* openwhisk-python-cli:
+    * API client written in Python.
+* openwhisk-package-pushnotifications:
+    * Push notifications to registered devices. (in-progress)
+* openwhisk-package-alarms
+    * Package that can be used to create periodic, time-based alarms
+* openwhisk-package-cloudant
+    * Package that can be used to store data to Cloudant object storage
+* openwhisk-package-twilio:
+    * Integration with Twilio. (in-progress)
+* openwhisk-package-jira:
+    * Integration with JIRA events. (in-progress)
+* openwhisk-package-rss:
+    * Integration with RSS feeds.
+* openwhisk-package-kafka:
+    * Integration with Kafka. (in-progress)
+* openwhisk-slackbot-poc:
+    * Deploy a Slackbot with the capability to run OpenWhisk actions.
+* openwhisk-wskdeploy:
+    * Utility for deploying and managing Apache OpenWhisk packages and projects.
+* openwhisk.github.io:
+    * Apache OpenWhisk website code built using Jekyll.
+* openwhisk-tutorial:
+    * Interactive tutorial framework (JavaScript) for for OpenWhisk, its CLI and packages.
+* openwhisk-vscode:
+    * Visual Studio Code extension (prototype) for authoring OpenWhisk actions inside the editor.
+* openwhisk-xcode:
+    * Collection of OpenWhisk tools for OS X implemented in Swift 3.
+* openwhisk-debugger
+    * The OpenWhisk debugger project
+* openwhisk-podspecs: 
+    * CocoaPods Podspecs repository for ‘openwhisk-client-swift’ subproject.
+* openwhisk-devtools:
+    * Development tools for building and deploying Apache OpenWhisk
+* openwhisk-workshop:
+    * Workshop to help developers learn how to build serverless applications using the platform.
+* openwhisk-package-template:
+    * This is a template to be use when creating new packages for OpenWhisk.
+* openwhisk-sample-matos:
+    * Sample application with Message Hub and Object Store.
+* openwhisk-sample-slackbot:
+    * A proof-of-concept Slackbot to invoke OpenWhisk actions.
+    
+The API Gateway code was a collaboration project jointly developed by both IBM Corporation
+and Adobe Systems Incorporated (http://www.adobe.com/) and a donation to the Apache OpenWhisk
+project by both companies.
+
+* openwhisk-apigateway: A performant API Gateway based on Openresty and NGINX.
+
+The OpenWhisk Composer project code was originally created, designed, developed (under an
+IBM copyright dated 2017-2018) and donated in October and November 2018
+by the IBM Corporation (http://www.ibm.com/) to the Apache Software Foundation (ASF).
+The following repositories were included in the donation:
+
+* openwhisk-composer:
+  * A new programming model for composing cloud functions built on OpenWhisk
+* openwhisk-composer-python:
+  * A Python client library for OpenWhisk composer.
+
+
+                          The Apache OpenWhisk Community
+                           http://openwhisk.apache.org/
diff --git a/openwhisk/Jenkinsfile b/openwhisk/Jenkinsfile
new file mode 100644
index 0000000..7f38635
--- /dev/null
+++ b/openwhisk/Jenkinsfile
@@ -0,0 +1,144 @@
+#!groovy
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+timeout(time: 12, unit: 'HOURS') {
+
+    def port = "444"
+    def cert = "domain.crt"
+    def key = "domain.key"
+
+    node("openwhisk") {
+        def hostName = sh(returnStdout: true, script: 'hostname').trim()
+        def domainName = hostName+".apache.org"
+        def home = sh(returnStdout: true, script: 'echo $HOME').trim()
+        def jobName = sh(returnStdout: true, script: 'echo $JOB_NAME').trim()
+        def jobSpace = "${home}/jenkins-slave/workspace/${jobName}"
+
+        lock("${hostName}") {
+            sh "mkdir -p ${jobSpace}"
+            dir("${jobSpace}") {
+                try {
+                    deleteDir()
+                    stage('Checkout') {
+                        checkout scm
+                    }
+
+                    stage('Build') {
+                        // Set up a private docker registry service, accessed by all the OpenWhisk VMs.
+                        try {
+                            sh "docker container stop registry && docker container rm -v registry"
+                        } catch (exp) {
+                            println("Unable to stop and remove the container registry.")
+                        }
+
+                        sh "docker run -d --restart=always --name registry -v \"$HOME\"/certs:/certs \
+                                -e REGISTRY_HTTP_ADDR=0.0.0.0:${port} -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/${cert} \
+                                -e REGISTRY_HTTP_TLS_KEY=/certs/${key} -p ${port}:${port} registry:2"
+                        // Build the controller, scheduler, and invoker images.
+                        sh "./gradlew distDocker -PdockerRegistry=${domainName}:${port}"
+                        //Install the various modules like standalone
+                        sh "./gradlew install"
+                    }
+
+                    stage('Deploy Lean') {
+                        dir("ansible") {
+                            // Copy the jenkins ansible configuration under the directory ansible. This can make sure the SSH is used to
+                            // access the VMs of invokers by the VM of the controller.
+                            sh '[ -f "environments/jenkins/ansible_jenkins.cfg" ] && cp environments/jenkins/ansible_jenkins.cfg ansible.cfg'
+                        }
+
+                        dir("ansible/environments/jenkins") {
+                            sh "cp ${hostName}.j2.ini hosts.j2.ini"
+                        }
+
+                        dir("ansible/environments/jenkins/group_vars") {
+                            sh "cp ${hostName} all"
+                        }
+
+                        dir("ansible") {
+                            sh 'ansible-playbook -i environments/jenkins setup.yml'
+                            sh 'ansible-playbook -i environments/jenkins openwhisk.yml -e mode=clean'
+                            sh 'ansible-playbook -i environments/jenkins apigateway.yml -e mode=clean'
+                            sh 'ansible-playbook -i environments/jenkins couchdb.yml -e mode=clean'
+                            sh 'ansible-playbook -i environments/jenkins couchdb.yml'
+                            sh 'ansible-playbook -i environments/jenkins initdb.yml'
+                            sh 'ansible-playbook -i environments/jenkins wipe.yml'
+                            sh 'ansible-playbook -i environments/jenkins apigateway.yml'
+                            sh 'ansible-playbook -i environments/jenkins openwhisk.yml -e lean=true'
+                            sh 'ansible-playbook -i environments/jenkins properties.yml'
+                            sh 'ansible-playbook -i environments/jenkins routemgmt.yml'
+                            sh 'ansible-playbook -i environments/jenkins postdeploy.yml'
+                        }
+                    }
+
+                    try {
+                        stage('Test Lean Openwhisk') {
+                            sh './gradlew :tests:test --tests system.basic.WskRestBasicTests -DtestResultsDirName=test-lean-openwhisk'
+                        }
+                    } catch (exp) {
+                        println("Exception: " + exp)
+                        error(exp)
+                    }
+
+                    stage('Deploy full Openwhisk') {
+                        dir("ansible") {
+                            sh 'ansible-playbook -i environments/jenkins openwhisk.yml -e mode=clean'
+                            sh 'ansible-playbook -i environments/jenkins openwhisk.yml'
+                        }
+                    }
+
+                    try {
+                        stage('Test') {
+                            sh './gradlew :tests:test -DtestResultsDirName=test-openwhisk'
+                        }
+                    } catch (exp) {
+                        println("Exception:" + exp)
+                    }
+
+                    try {
+                        stage('Shoot one invoker test') {
+                            def folder = "ansible/environments/jenkins/group_vars"
+                            def invoker1_node = sh(returnStdout: true,
+                                    script: "grep invoker1_machine ${folder}/${hostName} | cut -d: -f2").trim()
+                            sh "ssh -i ${home}/secret/openwhisk_key openwhisk@${invoker1_node} 'docker stop invoker1'"
+                            sleep time: 1, unit: 'MINUTES'
+                            sh './gradlew :tests:testShootInvoker -DtestResultsDirName=test-shoot-invoker'
+                            sh "ssh -i ${home}/secret/openwhisk_key openwhisk@${invoker1_node} 'docker start invoker1'"
+                        }
+                    } catch (exp) {
+                        println("Exception:" + exp)
+                    }
+
+                } catch (exp) {
+                    println("Exception:" + exp)
+		    error(exp)
+                } finally {
+                    println("Executing finally block")
+                    stage('Clean up') {
+                        dir("ansible") {
+                            sh 'ansible-playbook -i environments/jenkins openwhisk.yml -e mode=clean'
+                            sh 'ansible-playbook -i environments/jenkins apigateway.yml -e mode=clean'
+                            sh 'ansible-playbook -i environments/jenkins couchdb.yml -e mode=clean'
+                        }
+                    }
+                    step([$class: 'JUnitResultArchiver', testResults: '**/test*/**/TEST-*.xml'])
+                }
+            }
+        }
+    }
+}
diff --git a/openwhisk/LICENSE.txt b/openwhisk/LICENSE.txt
new file mode 100644
index 0000000..9335017
--- /dev/null
+++ b/openwhisk/LICENSE.txt
@@ -0,0 +1,218 @@
+
+                                 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.
+
+
+========================================================================
+Apache License 2.0
+========================================================================
+
+This distribution bundles the following components, which are available under an Apache License 2.0 (https://opensource.org/licenses/Apache-2.0).
+Spray Caching 1.3.4 (io.spray:spray-caching_2.11:1.3.4 - http://spray.io/documentation/1.2.4/spray-caching/)
+  ConcurrentMapBackedCache.scala under common/scala/src/main/scala/org/apache/openwhisk/core/database/ contains implementation
+  from Spray's [[spray.caching.Cache]] and [[spray.caching.SimpleLruCache]] respectively.
+  License included at licenses/LICENSE-spray.txt, or https://github.com/spray/spray/blob/master/LICENSE
+  Copyright (C) 2011-2015 the spray project <http://spray.io>
+
+This product bundles the files gradlew and gradlew.bat from Gradle v5.5
+which are distributed under the Apache License, Version 2.0.
+For details see ./gradlew and ./gradlew.bat.
diff --git a/openwhisk/NOTICE.txt b/openwhisk/NOTICE.txt
new file mode 100644
index 0000000..91c01f2
--- /dev/null
+++ b/openwhisk/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache OpenWhisk
+Copyright 2016-2021 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/openwhisk/README.md b/openwhisk/README.md
new file mode 100644
index 0000000..7198d26
--- /dev/null
+++ b/openwhisk/README.md
@@ -0,0 +1,119 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+# OpenWhisk
+
+[![Build Status](https://travis-ci.com/apache/openwhisk.svg?branch=master)](https://travis-ci.com/github/apache/openwhisk)
+[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+[![Join Slack](https://img.shields.io/badge/join-slack-9B69A0.svg)](https://openwhisk-team.slack.com/)
+[![codecov](https://codecov.io/gh/apache/openwhisk/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/openwhisk)
+[![Twitter](https://img.shields.io/twitter/follow/openwhisk.svg?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=openwhisk)
+
+OpenWhisk is a serverless functions platform for building cloud applications.
+OpenWhisk offers a rich programming model for creating serverless APIs from functions,
+composing functions into serverless workflows, and connecting events to functions using rules and triggers.
+Learn more at [http://openwhisk.apache.org](http://openwhisk.apache.org).
+
+* [Quick Start](#quick-start) (Deploy and Use OpenWhisk on your machine)
+* [Deploy to Kubernetes](#deploy-to-kubernetes) (For development and production)
+* For project contributors and Docker deployments:
+  * [Deploy to Docker for Mac](./tools/macos/README.md)
+  * [Deploy to Docker for Ubuntu](./tools/ubuntu-setup/README.md)
+* [Learn Concepts and Commands](#learn-concepts-and-commands)
+* [OpenWhisk Community and Support](#openwhisk-community-and-support)
+* [Project Repository Structure](#project-repository-structure)
+
+### Quick Start
+
+The easiest way to start using OpenWhisk is to install the "Standalone" OpenWhisk stack.
+This is a full-featured OpenWhisk stack running as a Java process for convenience.
+Serverless functions run within Docker containers. You will need [Docker](https://docs.docker.com/install),
+[Java](https://java.com/en/download/help/download_options.xml) and [Node.js](https://nodejs.org) available on your machine.
+
+To get started:
+```
+git clone https://github.com/apache/openwhisk.git
+cd openwhisk
+./gradlew core:standalone:bootRun
+```
+
+- When the OpenWhisk stack is up, it will open your browser to a functions [Playground](./docs/images/playground-ui.png),
+typically served from http://localhost:3232. The Playground allows you create and run functions directly from your browser.
+
+- To make use of all OpenWhisk features, you will need the OpenWhisk command line tool called
+`wsk` which you can download from https://s.apache.org/openwhisk-cli-download.
+Please refer to the [CLI configuration](./docs/cli.md) for additional details. Typically you
+configure the CLI for Standalone OpenWhisk as follows:
+```
+wsk property set \
+  --apihost 'http://localhost:3233' \
+  --auth '23bc46b1-71f6-4ed5-8c54-816aa4f8c502:123zO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP'
+```
+
+- Standalone OpenWhisk can be configured to deploy additional capabilities when that is desirable.
+Additional resources are available [here](./core/standalone/README.md).
+
+### Deploy to Kubernetes
+
+OpenWhisk can also be installed on a Kubernetes cluster. You can use
+a managed Kubernetes cluster provisioned from a public cloud provider
+(e.g., AKS, EKS, IKS, GKE), or a cluster you manage yourself.
+Additionally for local development, OpenWhisk is compatible with Minikube,
+and Kubernetes for Mac using the support built into Docker 18.06 (or higher).
+
+To get started:
+
+```
+git clone https://github.com/apache/openwhisk-deploy-kube.git
+```
+
+Then follow the instructions in the [OpenWhisk on Kubernetes README.md](https://github.com/apache/openwhisk-deploy-kube/blob/master/README.md).
+
+### Learn Concepts and Commands
+
+Browse the [documentation](docs/) to learn more. Here are some topics you may be
+interested in:
+
+- [System overview](docs/about.md)
+- [Getting Started](docs/README.md)
+- [Create and invoke actions](docs/actions.md)
+- [Create triggers and rules](docs/triggers_rules.md)
+- [Use and create packages](docs/packages.md)
+- [Browse and use the catalog](docs/catalog.md)
+- [OpenWhisk system details](docs/reference.md)
+- [Implementing feeds](docs/feeds.md)
+- [Developing a runtime for a new language](docs/actions-actionloop.md)
+
+### OpenWhisk Community and Support
+
+Report bugs, ask questions and request features [here on GitHub](../../issues).
+
+You can also join the OpenWhisk Team on Slack [https://openwhisk-team.slack.com](https://openwhisk-team.slack.com) and chat with developers. To get access to our public Slack team, request an invite [https://openwhisk.apache.org/slack.html](https://openwhisk.apache.org/slack.html).
+
+### Project Repository Structure
+
+The OpenWhisk system is built from a [number of components](docs/dev/modules.md).  The picture below groups the components by their GitHub repos. Please open issues for a component against the appropriate repo (if in doubt just open against the main openwhisk repo).
+
+![component/repo mapping](docs/images/components_to_repos.png)
+
+### What happens on an invocation?
+
+This diagram depicts the steps which take place within Openwhisk when an action is invoked by the user:
+
+![component/repo mapping](docs/images/Openwhisk-flow-diagram.png)
diff --git a/openwhisk/Taskfile.yml b/openwhisk/Taskfile.yml
new file mode 100644
index 0000000..ab10506
--- /dev/null
+++ b/openwhisk/Taskfile.yml
@@ -0,0 +1,106 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+version: '3'
+
+vars:
+  BASETAG: 3.1.0-mastrogpt
+  TAG:
+    sh: git describe --tags --abbrev=0 2>/dev/null || echo "no-tag"
+  TIMESTAMP:
+    sh: date +%y%m%d%H
+  S: ""
+  T: "all"
+
+tasks:
+  default: task --list-all
+  setup: {"silent": true}
+
+  yaml: 
+    silent: true
+    cmds: 
+     - |-
+       A="{{.CLI_ARGS}}"
+       if test -z "$A"
+       then ls -1 *.yml | sed -e 's/.yml//' -e 's/^/- /'
+       echo "edit(@)" or run  with '"task yaml -- [@]<script>"'
+       elif test "${A:0:1}" = "@"
+       then test -e ${A:1}.yml  && code ${A:1}.yml 
+            test -e roles/${A:1}/tasks/deploy.yml && code roles/${A:1}/tasks/deploy.yml
+       else ansible-playbook -i environments/local -e docker_image_prefix=testing "$A".yml
+       fi
+    dir: ansible
+
+  init:
+    cmds:
+     - task: clean
+     - rm -Rvf ../openwhisk-utilities
+     - ./tools/github/setup.sh
+
+  clean:
+   - docker ps -qa | xargs docker rm -f
+
+  tests:
+    silent: true
+    cmds: 
+     - |-
+       A="{{.CLI_ARGS}}"
+       if test -z "$A"
+       then ls tools/github/run* | sed -e 's!^.*run\(.*\)Tests.sh$!- \1!'
+       echo "edit(@)" or run  with '"task yaml -- [@]<script>"'
+       elif test "${A:0:1}" = "@"
+       then code tools/github/run${A:1}Tests.sh
+       else echo ./tools/github/run${A:1}Tests.sh
+       fi
+ 
+  setups:
+    silent: true
+    cmds: 
+     - |-
+       A="{{.CLI_ARGS}}"
+       if test -z "$A"
+       then ls tools/travis/setup* | sed -e 's!^.*setup\(..*\).sh$!- \1!'
+       echo "edit(@)" or run  with '"task setups -- [@]<script>"'
+       elif test "${A:0:1}" = "@"
+       then code tools/travis/setup${A:1}.sh
+       else echo ./tools/travis/setup${A:1}.sh
+       fi 
+
+  image-tag:
+    - cd ..; task image-tag ; git tag >.tag ; cd -
+    - git tag -d $(git tag)
+    - git tag $(cat ../.tag)-{{.T}}
+    - git tag
+
+  snap: 
+    silent: true
+    cmds:
+    - |-
+      TAG="{{.BASETAG}}.{{.TIMESTAMP}}{{.S}}-{{.T}}"
+      if test -n "{{.CLI_ARGS}}"
+      then git commit -m "{{.CLI_ARGS}}" -a
+      else
+      if [[ "$(git status -s | wc -l)" = "0" ]]
+      then 
+      git tag | xargs git tag -d
+      git tag -f $TAG
+      git push upstream --tags
+      else git status -s
+      echo commit with task snap -- message
+      echo push with task snap [T=test] [S=suffix]
+      fi
+      fi
diff --git a/openwhisk/ansible/README.md b/openwhisk/ansible/README.md
new file mode 100644
index 0000000..dc24251
--- /dev/null
+++ b/openwhisk/ansible/README.md
@@ -0,0 +1,540 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+Deploying OpenWhisk using Ansible
+=========
+
+
+### Getting started
+
+If you want to deploy OpenWhisk locally using Ansible, you first need to install Ansible on your development environment:
+
+#### Ubuntu users
+```shell script
+sudo apt-get install python-pip
+sudo pip install ansible==4.1.0
+sudo pip install jinja2==3.0.1
+```
+
+#### Docker for Mac users
+```shell script
+sudo easy_install pip
+sudo pip install ansible==4.1.0
+pip install jinja2==3.0.1
+```
+Docker for Mac does not provide any official ways to meet some requirements for OpenWhisk.
+You need to depend on the workarounds until Docker provides official methods.
+
+If you prefer [Docker-machine](https://docs.docker.com/machine/) to [Docker for mac](https://docs.docker.com/docker-for-mac/), you can follow instructions in [docker-machine/README.md](../tools/macos/docker-machine/README.md).
+
+##### Enable Docker remote API
+The remote Docker API is required for collecting logs using the Ansible playbook [logs.yml](logs.yml).
+
+##### Activate docker0 network (local dev only)
+ 
+The OpenWhisk deployment via Ansible uses the `docker0` network interface to deploy OpenWhisk and it does not exist on Docker for Mac environment.
+
+An expedient workaround is to add alias for `docker0` network to loopback interface.
+
+```shell script
+sudo ifconfig lo0 alias 172.17.0.1/24
+```
+
+### Using Ansible
+**Caveat:** All Ansible commands are meant to be executed from the `ansible` directory.
+This is important because that's where `ansible.cfg` is located which contains generic settings that are needed for the remaining steps.
+
+Set the environment for the commands below by running
+```shell script
+ENVIRONMENT=local  # or docker-machine or jenkins or vagrant
+```
+
+The default environment is `local` which works for Ubuntu and
+Docker for Mac. To use the default environment, you may omit the `-i` parameter entirely. For older Mac installation using Docker Machine,
+use `-i environments/docker-machine`.
+
+In all instructions, replace `<openwhisk_home>` with the base directory of your OpenWhisk source tree. e.g. `openwhisk`
+
+#### Ansible with pyenv (local dev only)
+
+When using [pyenv](https://github.com/pyenv/pyenv) to manage your versions of python, the [ansible python interpreter](https://docs.ansible.com/ansible/latest/reference_appendices/python_3_support.html) will use your system's default python, which may have a different version. 
+
+To make sure ansible uses the same version of python which you configured, execute: 
+
+```bash
+echo -e "\nansible_python_interpreter: `which python`\n" >> ./environments/local/group_vars/all
+```
+
+#### Preserving configuration and log directories on reboot
+When using the local Ansible environment, configuration and log data is stored in `/tmp` by default. However, operating
+system such as Linux and Mac clean the `/tmp` directory on reboot, resulting in failures when OpenWhisk tries to start
+up again. To avoid this problem, export the `OPENWHISK_TMP_DIR` variable assigning it the path to a persistent
+directory before deploying OpenWhisk.
+
+#### Setup
+ 
+This step should be executed once per development environment.
+It will generate the `hosts` configuration file based on your environment settings.
+
+> This file is generated automatically for an ephemeral CouchDB instance during `setup.yml`.
+
+The default configuration does not run multiple instances of core components (e.g., controller, invoker, kafka).
+You may elect to enable high-availability (HA) mode by passing tne Ansible option `-e mode=HA` when executing this playbook.
+This will configure your deployment with multiple instances (e.g., two Kafka instances, and two invokers).
+
+In addition to the host file generation, you need to configure the database for your deployment. This is done
+by modifying the file `ansible/db_local.ini` to provide the following properties.
+
+```
+[db_creds]
+db_provider=
+db_username=
+db_password=
+db_protocol=
+db_host=
+db_port=
+```
+
+For convenience, you can use shell environment variables that are read by the playbook to generate the required `db_local.ini` file as shown below.
+
+```shell script
+export OW_DB=CouchDB
+export OW_DB_USERNAME=<your couchdb user>
+export OW_DB_PASSWORD=<your couchdb password>
+export OW_DB_PROTOCOL=<your couchdb protocol>
+export OW_DB_HOST=<your couchdb host>
+export OW_DB_PORT=<your couchdb port>
+
+ansible-playbook -i environments/$ENVIRONMENT setup.yml
+```
+
+##### Use Cloudant as a datastore
+
+```shell script
+export OW_DB=Cloudant
+export OW_DB_USERNAME=<your cloudant user>
+export OW_DB_PASSWORD=<your cloudant password>
+export OW_DB_PROTOCOL=https
+export OW_DB_HOST=<your cloudant user>.cloudant.com
+export OW_DB_PORT=443
+
+ansible-playbook -i environments/$ENVIRONMENT setup.yml
+```
+
+#### Install Prerequisites
+
+> This step is not required for local environments since all prerequisites are already installed, and therefore may be skipped.
+
+This step needs to be done only once per target environment. It will install necessary prerequisites on all target hosts in the environment.
+
+```
+ansible-playbook -i environments/$ENVIRONMENT prereq.yml
+```
+
+**Hint:** During playbook execution the `TASK [prereq : check for pip]` can show as failed. This is normal if no pip is installed. The playbook will then move on and install pip on the target machines.
+
+### [Optional] Enable the new scheduler
+
+You can enable the new scheduler of OpenWhisk.
+It will run one more component called "scheduler" and ETCD.
+
+#### Configure service providers for the scheduler
+You can update service providers for the scheduler as follows. 
+
+**common/scala/src/main/resources/reference.conf**
+
+If you are using ElasticSearch (recommended) then replace ```NoopDurationCheckerProvider``` with ```ElasticSearchDurationCheckerProvider``` below.
+```
+whisk.spi {
+  ArtifactStoreProvider = org.apache.openwhisk.core.database.CouchDbStoreProvider
+  ActivationStoreProvider = org.apache.openwhisk.core.database.ArtifactActivationStoreProvider
+  MessagingProvider = org.apache.openwhisk.connector.kafka.KafkaMessagingProvider
+  ContainerFactoryProvider = org.apache.openwhisk.core.containerpool.docker.DockerContainerFactoryProvider
+  LogStoreProvider = org.apache.openwhisk.core.containerpool.logging.DockerToActivationLogStoreProvider
+  LoadBalancerProvider = org.apache.openwhisk.core.loadBalancer.FPCPoolBalancer
+  EntitlementSpiProvider = org.apache.openwhisk.core.entitlement.FPCEntitlementProvider
+  AuthenticationDirectiveProvider = org.apache.openwhisk.core.controller.BasicAuthenticationDirective
+  InvokerProvider = org.apache.openwhisk.core.invoker.FPCInvokerReactive
+  InvokerServerProvider = org.apache.openwhisk.core.invoker.FPCInvokerServer
+  DurationCheckerProvider = org.apache.openwhisk.core.scheduler.queue.NoopDurationCheckerProvider
+}
+.
+.
+.
+```
+#### Configure pause grace for the scheduler
+Set the value of pause-grace to 10s by default
+
+**core/invoker/src/main/resources/application.conf**
+```
+  container-proxy {
+    timeouts {
+      # The "unusedTimeout" in the ContainerProxy,
+      #aka 'How long should a container sit idle until we kill it?'
+      idle-container = 10 minutes
+      pause-grace = 10 seconds
+      keeping-duration = 10 minutes
+    }
+  .
+  .
+  .
+```
+
+#### Enable the scheduler
+- Make sure you enable the scheduler by configuring `scheduler_enable`.
+
+**ansible/environments/local/group_vars/all**
+```yaml
+scheduler_enable: true
+```
+
+#### [Optional] Enable ElasticSearch Activation Store
+When you use the new scheduler, it is recommended to use ElasticSearch as an activation store.
+
+**ansible/environments/local/group_vars**
+```yaml
+db_activation_backend: ElasticSearch
+elastic_cluster_name: <your elasticsearch cluster name>
+elastic_protocol: <your elasticsearch protocol>
+elastic_index_pattern: <your elasticsearch index pattern>
+elastic_base_volume: <your elasticsearch volume directory>
+elastic_username: <your elasticsearch username>
+elastic_password: <your elasticsearch username>
+```
+
+You can also refer to this guide to [deploy OpenWhisk using ElasticSearch](https://github.com/apache/openwhisk/blob/master/ansible/README.md#using-elasticsearch-to-store-activations).
+
+### Deploying Using CouchDB
+-   Make sure your `db_local.ini` file is [setup for](#setup) CouchDB then execute:
+
+```shell script
+cd <openwhisk_home>
+./gradlew distDocker
+cd ansible
+ansible-playbook -i environments/$ENVIRONMENT couchdb.yml
+ansible-playbook -i environments/$ENVIRONMENT initdb.yml
+ansible-playbook -i environments/$ENVIRONMENT wipe.yml
+ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml
+
+# installs a catalog of public packages and actions
+ansible-playbook -i environments/$ENVIRONMENT postdeploy.yml
+
+# to use the API gateway
+ansible-playbook -i environments/$ENVIRONMENT apigateway.yml
+ansible-playbook -i environments/$ENVIRONMENT routemgmt.yml
+```
+
+- You need to run `initdb.yml` **every time** you do a fresh deploy CouchDB to initialize the subjects database.
+- The `wipe.yml` playbook should be run on a fresh deployment only, otherwise actions and activations will be lost.
+- Run `postdeploy.yml` after deployment to install a catalog of useful packages.
+- To use the API Gateway, you'll need to run `apigateway.yml` and `routemgmt.yml`.
+- Use `ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml` to avoid wiping the data store. This is useful to start OpenWhisk after restarting your Operating System.
+
+#### Limitation
+
+You cannot run multiple CouchDB nodes on a single machine. This limitation comes from Erlang EPMD which CouchDB relies on to find other nodes.
+To deploy multiple CouchDB nodes, they should be placed on different machines respectively otherwise their ports will clash.
+
+
+### Deploying Using Cloudant
+-   Make sure your `db_local.ini` file is set up for Cloudant. See [Setup](#setup).
+-   Then execute:
+
+```shell script
+cd <openwhisk_home>
+./gradlew distDocker
+cd ansible
+ansible-playbook -i environments/$ENVIRONMENT initdb.yml
+ansible-playbook -i environments/$ENVIRONMENT wipe.yml
+ansible-playbook -i environments/$ENVIRONMENT apigateway.yml
+ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml
+
+# installs a catalog of public packages and actions
+ansible-playbook -i environments/$ENVIRONMENT postdeploy.yml
+
+# to use the API gateway
+ansible-playbook -i environments/$ENVIRONMENT apigateway.yml
+ansible-playbook -i environments/$ENVIRONMENT routemgmt.yml
+```
+
+- You need to run `initdb` on Cloudant **only once** per Cloudant database to initialize the subjects database.
+- The `initdb.yml` playbook will only initialize your database if it is not initialized already, else it will skip initialization steps.
+- The `wipe.yml` playbook should be run on a fresh deployment only, otherwise actions and activations will be lost.
+- Run `postdeploy.yml` after deployment to install a catalog of useful packages.
+- To use the API Gateway, you'll need to run `apigateway.yml` and `routemgmt.yml`.
+- Use `ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml` to avoid wiping the data store. This is useful to start OpenWhisk after restarting your Operating System.
+
+### Deploying Using MongoDB
+
+You can choose MongoDB instead of CouchDB as the database backend to store entities.
+
+- Deploy a mongodb server(Optional, for test and develop only, use an external MongoDB server in production).
+  You need to execute `pip install pymongo` first
+
+```
+ansible-playbook -i environments/<environment> mongodb.yml -e mongodb_data_volume="/tmp/mongo-data"
+```
+
+- Then execute
+
+```
+cd <openwhisk_home>
+./gradlew distDocker
+cd ansible
+ansible-playbook -i environments/<environment> initMongodb.yml -e mongodb_connect_string="mongodb://172.17.0.1:27017"
+ansible-playbook -i environments/<environment> apigateway.yml -e mongodb_connect_string="mongodb://172.17.0.1:27017"
+ansible-playbook -i environments/<environment> openwhisk.yml -e mongodb_connect_string="mongodb://172.17.0.1:27017" -e db_artifact_backend="MongoDB"
+
+# installs a catalog of public packages and actions
+ansible-playbook -i environments/<environment> postdeploy.yml
+
+# to use the API gateway
+ansible-playbook -i environments/<environment> apigateway.yml
+ansible-playbook -i environments/<environment> routemgmt.yml
+```
+
+Available parameters for ansible are
+```
+  mongodb:
+    connect_string: "{{ mongodb_connect_string }}"
+    database: "{{ mongodb_database | default('whisks') }}"
+    data_volume: "{{ mongodb_data_volume | default('mongo-data') }}"
+```
+
+### Using ElasticSearch to Store Activations
+
+You can use ElasticSearch (ES) to store activations separately while other entities remain stored in CouchDB. There is an Ansible playbook to setup a simple ES cluster for testing and development purposes.
+
+-   Provide your custom ES related ansible arguments:
+
+```
+elastic_protocol="http"
+elastic_index_pattern="openwhisk-%s" // this will be combined with namespace's name, so different namespace can use different index
+elastic_base_volume="esdata" // name of docker volume to store ES data
+elastic_cluster_name="openwhisk"
+elastic_java_opts="-Xms1g -Xmx1g"
+elastic_loglevel="INFO"
+elastic_username="admin"
+elastic_password="admin"
+elasticsearch_connect_string="x.x.x.x:9200,y.y.y.y:9200" // if you want to use an external ES cluster, add it
+```
+
+-  Then execute:
+
+```shell script
+cd <openwhisk_home>
+./gradlew distDocker
+cd ansible
+# couchdb is still needed to store subjects and actions
+ansible-playbook -i environments/$ENVIRONMENT couchdb.yml
+ansible-playbook -i environments/$ENVIRONMENT initdb.yml
+ansible-playbook -i environments/$ENVIRONMENT wipe.yml
+# this will deploy a simple ES cluster, you can skip this to use external ES cluster
+ansible-playbook -i environments/$ENVIRONMENT elasticsearch.yml
+ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml -e db_activation_backend=ElasticSearch
+
+# installs a catalog of public packages and actions
+ansible-playbook -i environments/$ENVIRONMENT postdeploy.yml
+
+# to use the API gateway
+ansible-playbook -i environments/$ENVIRONMENT apigateway.yml
+ansible-playbook -i environments/$ENVIRONMENT routemgmt.yml
+```
+
+### Configuring the installation of `wsk` CLI
+There are two installation modes to install `wsk` CLI: remote and local.
+
+The mode "remote" means to download the `wsk` binaries from available web links.
+By default, OpenWhisk sets the installation mode to remote and downloads the
+binaries from the CLI
+[release page](https://github.com/apache/openwhisk-cli/releases),
+where OpenWhisk publishes the official `wsk` binaries.
+
+The mode "local" means to build and install the `wsk` binaries from local CLI
+project. You can download the source code of OpenWhisk CLI
+[here](https://github.com/apache/openwhisk-cli).
+Let's assume your OpenWhisk CLI home directory is
+`$OPENWHISK_HOME/../openwhisk-cli` and you've already `export`ed
+`OPENWHISK_HOME` to be the root directory of this project. After you download
+the CLI repository, use the gradle command to build the binaries (you can omit
+the `-PnativeBuild` if you want to cross-compile for all supported platforms):
+
+```shell script
+cd "$OPENWHISK_HOME/../openwhisk-cli"
+./gradlew releaseBinaries -PnativeBuild
+```
+
+The binaries are generated and put into a tarball in the folder
+`../openwhisk-cli/release`.  Then, use the following Ansible command
+to (re-)configure the CLI installation:
+
+```shell script
+export OPENWHISK_ENVIRONMENT=local  # ... or whatever
+ansible-playbook -i environments/$OPENWHISK_ENVIRONMENT edge.yml -e mode=clean
+ansible-playbook -i environments/$OPENWHISK_ENVIRONMENT edge.yml \
+    -e cli_installation_mode=local \
+    -e openwhisk_cli_home="$OPENWHISK_HOME/../openwhisk-cli"
+```
+
+The parameter `cli_installation_mode` specifies the CLI installation mode and
+the parameter `openwhisk_cli_home` specifies the home directory of your local
+OpenWhisk CLI.  (_n.b._ `openwhisk_cli_home` defaults to
+`$OPENWHISK_HOME/../openwhisk-cli`.)
+
+Once the CLI is installed, you can [use it to work with Whisk](../docs/cli.md).
+
+### Hot-swapping a Single Component
+The playbook structure allows you to clean, deploy or re-deploy a single component as well as the entire OpenWhisk stack. Let's assume you have deployed the entire stack using the `openwhisk.yml` playbook. You then make a change to a single component, for example the invoker. You will probably want a new tag on the invoker image so you first build it using:
+
+```shell script
+cd <openwhisk_home>
+./gradlew :core:invoker:distDocker -PdockerImageTag=myNewInvoker
+```
+Then all you need to do is re-deploy the invoker using the new image:
+
+```shell script
+cd ansible
+ansible-playbook -i environments/$ENVIRONMENT invoker.yml -e docker_image_tag=myNewInvoker
+```
+
+**Hint:** You can omit the Docker image tag parameters in which case `latest` will be used implicitly.
+
+### Cleaning a Single Component
+You can remove a single component just as you would remove the entire deployment stack.
+For example, if you wanted to remove only the controller you would run:
+
+```shell script
+cd ansible
+ansible-playbook -i environments/$ENVIRONMENT controller.yml -e mode=clean
+```
+
+**Caveat:** In distributed environments some components (e.g. Invoker, etc.) exist on multiple machines. So if you run a playbook to clean or deploy those components, it will run on **all** of the hosts targeted by the component's playbook.
+
+
+### Cleaning an OpenWhisk Deployment
+Once you are done with the deployment you can clean it from the target environment.
+
+```shell script
+ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml -e mode=clean
+```
+
+### Removing all prereqs from an environment
+This is usually not necessary, however in case you want to uninstall all prereqs from a target environment, execute:
+
+```shell script
+ansible-playbook -i environments/$ENVIRONMENT prereq.yml -e mode=clean
+```
+
+### Lean Setup
+To have a lean setup (no Kafka, Zookeeper and no Invokers as separate entities):
+
+At [Deploying Using CouchDB](ansible/README.md#deploying-using-cloudant) step, replace:
+```shell script
+ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml
+```
+by:
+```shell script
+ansible-playbook -i environments/$ENVIRONMENT openwhisk.yml -e lean=true
+```
+
+### Troubleshooting
+Some of the more common problems and their solution are listed here.
+
+#### Setuptools Version Mismatch
+If you encounter the following error message during `ansible` execution
+
+```
+ERROR! Unexpected Exception: ... Requirement.parse('setuptools>=11.3'))
+```
+
+your `setuptools` package is likely out of date. You can upgrade the package using this command:
+
+```shell script
+pip install --upgrade setuptools --user python
+```
+
+
+#### Mac Setup - Python Interpreter
+The MacOS environment assumes Python is installed in `/usr/local/bin` which is the default location when using `brew`.
+The following error will occur if Python is located elsewhere:
+
+```
+ansible all -i environments/mac -m ping
+ansible | FAILED! => {
+    "changed": false,
+    "failed": true,
+    "module_stderr": "/bin/sh: /usr/local/bin/python: No such file or directory\n",
+    "module_stdout": "",
+    "msg": "MODULE FAILURE",
+    "parsed": false
+}
+```
+
+An expedient workaround is to create a link to the expected location:
+
+```shell script
+ln -s $(which python) /usr/local/bin/python
+```
+
+Alternatively, you can also configure the location of Python interpreter in `environments/<environment>/group_vars`.
+
+```shell script
+ansible_python_interpreter: "/usr/local/bin/python"
+```
+
+#### Failed to import docker-py
+
+After `brew install ansible`, the following lines are printed out:
+
+```
+==> Caveats
+If you need Python to find the installed site-packages:
+  mkdir -p ~/Library/Python/2.7/lib/python/site-packages
+  echo '/usr/local/lib/python2.7/site-packages' > ~/Library/Python/2.7/lib/python/site-packages/homebrew.pth
+```
+
+Just run the two commands to fix this issue.
+
+#### Spaces in Paths
+Ansible 2.1.0.0 and earlier versions do not support a space in file paths.
+Many file imports and roles will not work correctly when included from a path that contains spaces.
+If you encounter this error message during Ansible execution
+
+```
+fatal: [ansible]: FAILED! => {"failed": true, "msg": "need more than 1 value to unpack"}
+```
+
+the path to your OpenWhisk `ansible` directory contains spaces. To fix this, please copy the source tree to a path
+without spaces as there is no current fix available to this problem.
+
+#### Changing limits
+The default system throttling limits are configured in this file [./group_vars/all](./group_vars/all) and may be changed by modifying the group_vars for your specific environment.
+```
+limits:
+  invocationsPerMinute: "{{ limit_invocations_per_minute | default(60) }}"
+  concurrentInvocations: "{{ limit_invocations_concurrent | default(30) }}"
+  firesPerMinute: "{{ limit_fires_per_minute | default(60) }}"
+  sequenceMaxLength: "{{ limit_sequence_max_length | default(50) }}"
+```
+- The `limits.invocationsPerMinute` represents the allowed namespace action invocations per minute.
+- The `limits.concurrentInvocations` represents the maximum concurrent invocations allowed per namespace.
+- The `limits.firesPerMinute` represents the allowed namespace trigger firings per minute.
+- The `limits.sequenceMaxLength` represents the maximum length of a sequence action.
+
+#### Set the timezone for containers
+The default timezone for all system containers is UTC. The timezone may differ from your servers which could make it difficult to inspect logs. The timezone is configured globally in [group_vars/all](./group_vars/all#L280) or by passing an extra variable `-e docker_timezone=xxx` when you run an ansible-playbook.
diff --git a/openwhisk/ansible/ansible.cfg b/openwhisk/ansible/ansible.cfg
new file mode 100644
index 0000000..db7066d
--- /dev/null
+++ b/openwhisk/ansible/ansible.cfg
@@ -0,0 +1,12 @@
+[defaults]
+
+callback_whitelist = profile_tasks
+retry_files_enabled = False
+host_key_checking = False
+inventory = environments/local
+callback_plugins = callbacks
+hash_behaviour = merge
+gather_timeout = 60
+
+[ssh_connection]
+scp_if_ssh = True
diff --git a/openwhisk/ansible/apigateway.yml b/openwhisk/ansible/apigateway.yml
new file mode 100644
index 0000000..7125cb5
--- /dev/null
+++ b/openwhisk/ansible/apigateway.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+- hosts: redis
+  roles:
+  - redis
+
+- hosts: apigateway
+  roles:
+  - apigateway
diff --git a/openwhisk/ansible/callbacks/logformatter.py b/openwhisk/ansible/callbacks/logformatter.py
new file mode 100644
index 0000000..c2aabb2
--- /dev/null
+++ b/openwhisk/ansible/callbacks/logformatter.py
@@ -0,0 +1,92 @@
+"""Python callback for highlighting Ansible logs.
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+"""
+from __future__ import (absolute_import, division, print_function)
+import os
+import sys
+import textwrap
+from ansible.plugins.callback import CallbackBase
+__metaclass__ = type
+
+
+class CallbackModule(CallbackBase):
+    """."""
+
+    def __init__(self):
+        """Initialize superclass."""
+        super(CallbackModule, self).__init__()
+
+    def emit(self, host, category, data):
+        """Emit colorized output based upon data contents."""
+        if type(data) == dict:
+            cmd = data['cmd'] if 'cmd' in data else None
+            msg = data['msg'] if 'msg' in data else None
+            stdout = data['stdout'] if 'stdout' in data else None
+            stderr = data['stderr'] if 'stderr' in data else None
+            reason = data['reason'] if 'reason' in data else None
+
+            print()
+            if cmd:
+                print(hilite('[%s]\n> %s' % (category, cmd), category, wrap = False))
+            if reason:
+                print(hilite(reason, category))
+            if msg:
+                print(hilite(msg, category))
+            if stdout:
+                print(hilite(stdout, category))
+            if stderr:
+                print(hilite(stderr, category))
+
+    def runner_on_failed(self, host, res, ignore_errors=False):
+        self.emit(host, 'FAILED', res)
+
+    def runner_on_ok(self, host, res):
+        pass
+
+    def runner_on_skipped(self, host, item=None):
+        self.emit(host, 'SKIPPED', '...')
+
+    def runner_on_unreachable(self, host, res):
+        self.emit(host, 'UNREACHABLE', res)
+
+    def runner_on_async_failed(self, host, res, jid):
+        self.emit(host, 'FAILED', res)
+
+
+def hilite(msg, status, wrap = True):
+    """Highlight message."""
+    def supports_color():
+        if ((sys.platform != 'win32' or 'ANSICON' in os.environ) and
+           sys.stdout.isatty()):
+            return True
+        else:
+            return False
+
+    if supports_color():
+        attr = []
+        if status == 'FAILED':
+            # red
+            attr.append('31')
+        else:
+            # bold
+            attr.append('1')
+        text = '\x1b[%sm%s\x1b[0m' % (';'.join(attr), msg)
+    else:
+        text = msg
+    return textwrap.fill(text, 80) if wrap else text
diff --git a/openwhisk/ansible/controller.yml b/openwhisk/ansible/controller.yml
new file mode 100644
index 0000000..dbd8c02
--- /dev/null
+++ b/openwhisk/ansible/controller.yml
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys Openwhisk Controllers.
+
+- hosts: controllers
+  vars:
+    #
+    # host_group - usually "{{ groups['...'] }}" where '...' is what was used
+    #   for 'hosts' above.  The hostname of each host will be looked up in this
+    #   group to assign a zero-based index.  That index will be used in concert
+    #   with 'name_prefix' below to assign a host/container name.
+    host_group: "{{ groups['controllers'] }}"
+    #
+    # name_prefix - a unique prefix for this set of controllers.  The prefix
+    #   will be used in combination with an index (determined using
+    #   'host_group' above) to name host/controllers.
+    name_prefix: "controller"
+    #
+    # controller_index_base - the deployment process allocates host docker
+    #   ports to individual controllers based on their indices.  This is an
+    #   additional offset to prevent collisions between different controller
+    #   groups. Usually 0 if only one group is being deployed, otherwise
+    #   something like "{{ groups['firstcontrollergroup']|length }}"
+    controller_index_base: 0
+    #
+    # select which additional capabilities (from the controller role) need
+    #   to be added to the controller.  Plugin will override default
+    #   configuration settings.  (Plugins are found in the
+    #   'roles/controller/tasks' directory for now.)
+    controller_plugins:
+      # Join an akka cluster rather than running standalone akka
+      - "join_akka_cluster"
+
+  roles:
+    - controller
diff --git a/openwhisk/ansible/couchdb.yml b/openwhisk/ansible/couchdb.yml
new file mode 100644
index 0000000..d895c55
--- /dev/null
+++ b/openwhisk/ansible/couchdb.yml
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys a CouchDB for Openwhisk.
+
+- hosts: db
+  roles:
+  - couchdb
diff --git a/openwhisk/ansible/downloadcli-github.yml b/openwhisk/ansible/downloadcli-github.yml
new file mode 100644
index 0000000..967b910
--- /dev/null
+++ b/openwhisk/ansible/downloadcli-github.yml
@@ -0,0 +1,31 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook is used to download cli from Github and copy directly to bin
+
+- hosts: ansible
+  tasks:
+  - name: grab the local CLI from the Github
+    unarchive:
+      src: "{{ openwhisk_cli.remote.location }}/{{ openwhisk_cli.archive_name}}-{{ openwhisk_cli_tag }}-{{os}}-{{arch}}.{{ext}}"
+      dest: "{{ openwhisk_home }}/bin"
+      mode: "0755"
+      remote_src: yes
+    vars:
+      arch: "{{ ansible_machine | replace ('x86_64', 'amd64') }}"
+      os: "{{ ansible_system | lower | replace('darwin', 'mac') }}"
+      ext: "{{ ( ansible_system in ['Windows', 'Darwin']) | ternary('zip', 'tgz') }}"
diff --git a/openwhisk/ansible/downloadcli.yml b/openwhisk/ansible/downloadcli.yml
new file mode 100644
index 0000000..fa01904
--- /dev/null
+++ b/openwhisk/ansible/downloadcli.yml
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook downloads the Openwhisk cli aka wsk from the API host.
+
+- hosts: ansible
+  roles:
+  - cli-install
diff --git a/openwhisk/ansible/edge.yml b/openwhisk/ansible/edge.yml
new file mode 100644
index 0000000..0e3aafd
--- /dev/null
+++ b/openwhisk/ansible/edge.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys Openwhisk Edge servers.
+# The edge is usually populated with NGINX serving as proxy.
+# The CLI also gets built and published for downloading from NGINX.
+# SDKs for blackbox get published to NGINX also.
+
+- hosts: edge
+  roles:
+  - nginx
+  - cli
diff --git a/openwhisk/ansible/elasticsearch.yml b/openwhisk/ansible/elasticsearch.yml
new file mode 100644
index 0000000..ed05e3d
--- /dev/null
+++ b/openwhisk/ansible/elasticsearch.yml
@@ -0,0 +1,34 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys a ElasticSearch cluster
+
+- hosts: elasticsearch
+  vars:
+    #
+    # host_group - usually "{{ groups['...'] }}" where '...' is what was used
+    #   for 'hosts' above.  The hostname of each host will be looked up in this
+    #   group to assign a zero-based index.  That index will be used in concert
+    #   with 'name_prefix' below to assign a host/container name.
+    host_group: "{{ groups['elasticsearch'] }}"
+    #
+    # name_prefix - a unique prefix for this set of elasticsearches.  The prefix
+    # will be used in combination with an index (determined using
+    # 'host_group' above) to name host/elasticsearcher.
+    name_prefix: "elasticsearch"
+  roles:
+  - elasticsearch
diff --git a/openwhisk/ansible/environments/docker-machine/group_vars/all b/openwhisk/ansible/environments/docker-machine/group_vars/all
new file mode 100644
index 0000000..bc6d58b
--- /dev/null
+++ b/openwhisk/ansible/environments/docker-machine/group_vars/all
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+config_root_dir: /Users/Shared/wskconf
+whisk_logs_dir: /Users/Shared/wsklogs
+docker_registry: ""
+docker_dns: ""
+runtimes_bypass_pull_for_local_images: true
+
+environment_type: "docker-machine"
+
+env_hosts_dir: "{{ playbook_dir }}/environments/docker-machine"
+
+# The whisk_api_localhost_name is used to configure nginx to permit vanity URLs for web actions.
+# It is also used for the SSL certificate generation. For a local deployment, this is typically
+# a hostname that is resolved on the client, via /etc/hosts for example.
+whisk_api_localhost_name: "openwhisk"
+
+# Hardcoded for docker-machine since db init runs on host not inside VM
+db_prefix: whisk_dockermachine_
+
+# API GW connection configuration
+apigw_auth_user: ""
+apigw_auth_pwd: ""
+apigw_host_v2: "http://{{ groups['apigateway']|first }}:{{apigateway.port.api}}/v2"
+
+invoker_allow_multiple_instances: true
+
+# Set kafka configuration
+kafka_heap: '512m'
+kafka_topics_completed_retentionBytes: 104857600
+kafka_topics_completed_retentionMS: 300000
+kafka_topics_health_retentionBytes: 104857600
+kafka_topics_health_retentionMS: 300000
+kafka_topics_invoker_retentionBytes: 104857600
+kafka_topics_invoker_retentionMS: 300000
diff --git a/openwhisk/ansible/environments/docker-machine/hosts.j2.ini b/openwhisk/ansible/environments/docker-machine/hosts.j2.ini
new file mode 100644
index 0000000..e1549e6
--- /dev/null
+++ b/openwhisk/ansible/environments/docker-machine/hosts.j2.ini
@@ -0,0 +1,52 @@
+; the first parameter in a host is the inventory_hostname
+
+; used for local actions only
+ansible ansible_connection=local
+
+[edge]
+{{ docker_machine_ip }}     ansible_host={{ docker_machine_ip }}
+
+[controllers]
+controller0                 ansible_host={{ docker_machine_ip }}
+;{% if mode is defined and 'HA' in mode %}
+;controller1                 ansible_host={{ docker_machine_ip }}
+;{% endif %}
+
+[kafkas]
+kafka0                      ansible_host={{ docker_machine_ip }}
+{% if mode is defined and 'HA' in mode %}
+kafka1                      ansible_host={{ docker_machine_ip }}
+{% endif %}
+
+[zookeepers:children]
+kafkas
+
+[invokers]
+invoker0                    ansible_host={{ docker_machine_ip }}
+{% if mode is defined and 'HA' in mode %}
+invoker1                    ansible_host={{ docker_machine_ip }}
+{% endif %}
+
+[db]
+{{ docker_machine_ip }}     ansible_host={{ docker_machine_ip }}
+
+[redis]
+{{ docker_machine_ip }}     ansible_host={{ docker_machine_ip }}
+
+[apigateway]
+{{ docker_machine_ip }}     ansible_host={{ docker_machine_ip }}
+
+[elasticsearch:children]
+db
+
+[etcd]
+etcd0            ansible_host={{ docker_machine_ip }}
+{% if mode is defined and 'HA' in mode %}
+etcd1            ansible_host={{ docker_machine_ip }}
+
+; define variables
+[all:vars]
+ansible_connection=ssh
+ansible_user=docker
+ansible_ssh_private_key_file=~/.docker/machine/machines/{{ docker_machine_name | default("whisk")}}/id_rsa
+ansible_python_interpreter=/usr/local/bin/python
diff --git a/openwhisk/ansible/environments/jenkins/ansible_jenkins.cfg b/openwhisk/ansible/environments/jenkins/ansible_jenkins.cfg
new file mode 100644
index 0000000..55fd45b
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/ansible_jenkins.cfg
@@ -0,0 +1,13 @@
+[defaults]
+
+callback_whitelist = profile_tasks
+retry_files_enabled = False
+host_key_checking = False
+inventory = environments/jenkins
+callback_plugins = callbacks
+hash_behaviour = merge
+remote_user = openwhisk
+private_key_file=/home/jenkins/secret/openwhisk_key
+
+[ssh_connection]
+scp_if_ssh = True
diff --git a/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm1-he-de b/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm1-he-de
new file mode 100644
index 0000000..07c5273
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm1-he-de
@@ -0,0 +1,53 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+openwhisk_tmp_dir: "{{ lookup('env', 'OPENWHISK_TMP_DIR')|default('/tmp', true) }}"
+config_root_dir: "{{ openwhisk_tmp_dir }}/wskconf"
+whisk_logs_dir: "{{ openwhisk_tmp_dir }}/wsklogs"
+coverage_enabled: "{{ lookup('env', 'GRADLE_COVERAGE') | default('false', true) | bool}}"
+coverage_logs_dir: "{{ openwhisk_tmp_dir }}/wskcov"
+docker_registry: "openwhisk-vm1-he-de.apache.org:444/"
+docker_dns: ""
+runtimes_bypass_pull_for_local_images: true
+invoker_use_runc: "{{ ansible_distribution != 'MacOSX' }}"
+
+db_prefix: whisk_local_
+
+# API GW connection configuration
+apigw_auth_user: ""
+apigw_auth_pwd: ""
+apigw_host_v2: "http://{{ groups['apigateway']|first }}:{{apigateway.port.api}}/v2"
+
+invoker_allow_multiple_instances: true
+
+# Set kafka configuration
+kafka_heap: '512m'
+kafka_topics_completed_retentionBytes: 104857600
+kafka_topics_completed_retentionMS: 300000
+kafka_topics_health_retentionBytes: 104857600
+kafka_topics_health_retentionMS: 300000
+kafka_topics_invoker_retentionBytes: 104857600
+kafka_topics_invoker_retentionMS: 300000
+
+env_hosts_dir: "{{ playbook_dir }}/environments/jenkins"
+
+container_pool_akka_client: true
+runtimes_enable_concurrency: true
+limit_action_concurrency_max: 500
+
+invoker1_machine: openwhisk-vm3-he-de
+invoker_use_runc: false
diff --git a/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm2-he-de b/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm2-he-de
new file mode 100644
index 0000000..c8c6b7e
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm2-he-de
@@ -0,0 +1,54 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+openwhisk_tmp_dir: "{{ lookup('env', 'OPENWHISK_TMP_DIR')|default('/tmp', true) }}"
+config_root_dir: "{{ openwhisk_tmp_dir }}/wskconf"
+whisk_logs_dir: "{{ openwhisk_tmp_dir }}/wsklogs"
+coverage_enabled: "{{ lookup('env', 'GRADLE_COVERAGE') | default('false', true) | bool}}"
+coverage_logs_dir: "{{ openwhisk_tmp_dir }}/wskcov"
+docker_registry: "openwhisk-vm2-he-de.apache.org:444/"
+docker_dns: ""
+runtimes_bypass_pull_for_local_images: true
+invoker_use_runc: "{{ ansible_distribution != 'MacOSX' }}"
+
+db_prefix: whisk_local_
+
+# API GW connection configuration
+apigw_auth_user: ""
+apigw_auth_pwd: ""
+apigw_host_v2: "http://{{ groups['apigateway']|first }}:{{apigateway.port.api}}/v2"
+
+invoker_allow_multiple_instances: true
+
+# Set kafka configuration
+kafka_heap: '512m'
+kafka_topics_completed_retentionBytes: 104857600
+kafka_topics_completed_retentionMS: 300000
+kafka_topics_health_retentionBytes: 104857600
+kafka_topics_health_retentionMS: 300000
+kafka_topics_invoker_retentionBytes: 104857600
+kafka_topics_invoker_retentionMS: 300000
+
+env_hosts_dir: "{{ playbook_dir }}/environments/jenkins"
+
+container_pool_akka_client: true
+runtimes_enable_concurrency: true
+limit_action_concurrency_max: 500
+limit_invocations_per_minute: 120
+
+invoker1_machine: openwhisk-vm1-he-de
+invoker_use_runc: false
diff --git a/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm3-he-de b/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm3-he-de
new file mode 100644
index 0000000..9558d5b
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/group_vars/openwhisk-vm3-he-de
@@ -0,0 +1,53 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+openwhisk_tmp_dir: "{{ lookup('env', 'OPENWHISK_TMP_DIR')|default('/tmp', true) }}"
+config_root_dir: "{{ openwhisk_tmp_dir }}/wskconf"
+whisk_logs_dir: "{{ openwhisk_tmp_dir }}/wsklogs"
+coverage_enabled: "{{ lookup('env', 'GRADLE_COVERAGE') | default('false', true) | bool}}"
+coverage_logs_dir: "{{ openwhisk_tmp_dir }}/wskcov"
+docker_registry: "openwhisk-vm3-he-de.apache.org:444/"
+docker_dns: ""
+runtimes_bypass_pull_for_local_images: true
+invoker_use_runc: "{{ ansible_distribution != 'MacOSX' }}"
+
+db_prefix: whisk_local_
+
+# API GW connection configuration
+apigw_auth_user: ""
+apigw_auth_pwd: ""
+apigw_host_v2: "http://{{ groups['apigateway']|first }}:{{apigateway.port.api}}/v2"
+
+invoker_allow_multiple_instances: true
+
+# Set kafka configuration
+kafka_heap: '512m'
+kafka_topics_completed_retentionBytes: 104857600
+kafka_topics_completed_retentionMS: 300000
+kafka_topics_health_retentionBytes: 104857600
+kafka_topics_health_retentionMS: 300000
+kafka_topics_invoker_retentionBytes: 104857600
+kafka_topics_invoker_retentionMS: 300000
+
+env_hosts_dir: "{{ playbook_dir }}/environments/jenkins"
+
+container_pool_akka_client: true
+runtimes_enable_concurrency: true
+limit_action_concurrency_max: 500
+
+invoker1_machine: openwhisk-vm2-he-de
+invoker_use_runc: false
diff --git a/openwhisk/ansible/environments/jenkins/openwhisk-vm1-he-de.j2.ini b/openwhisk/ansible/environments/jenkins/openwhisk-vm1-he-de.j2.ini
new file mode 100644
index 0000000..a6c6cb7
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/openwhisk-vm1-he-de.j2.ini
@@ -0,0 +1,36 @@
+; the first parameter in a host is the inventory_hostname
+
+; used for local actions only
+ansible ansible_connection=local
+
+[edge]
+144.76.63.94          ansible_host=144.76.63.94 ansible_connection=local
+
+[controllers]
+controller0         ansible_host=144.76.63.94 ansible_connection=local
+;{% if mode is defined and 'HA' in mode %}
+;controller1         ansible_host=144.76.63.94 ansible_connection=local
+;{% endif %}
+
+[kafkas]
+kafka0              ansible_host=144.76.63.94 ansible_connection=local
+{% if mode is defined and 'HA' in mode %}
+kafka1              ansible_host=144.76.63.94 ansible_connection=local
+{% endif %}
+
+[zookeepers:children]
+kafkas
+
+[invokers]
+invoker0            ansible_host=5.9.20.92 ansible_connection=ssh
+invoker1            ansible_host=144.76.27.183 ansible_connection=ssh
+
+; db group is only used if db.provider is CouchDB
+[db]
+144.76.63.94         ansible_host=144.76.63.94 ansible_connection=local
+
+[redis]
+144.76.63.94          ansible_host=144.76.63.94 ansible_connection=local
+
+[apigateway]
+144.76.63.94          ansible_host=144.76.63.94 ansible_connection=local
diff --git a/openwhisk/ansible/environments/jenkins/openwhisk-vm2-he-de.j2.ini b/openwhisk/ansible/environments/jenkins/openwhisk-vm2-he-de.j2.ini
new file mode 100644
index 0000000..b6be1a4
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/openwhisk-vm2-he-de.j2.ini
@@ -0,0 +1,36 @@
+; the first parameter in a host is the inventory_hostname
+
+; used for local actions only
+ansible ansible_connection=local
+
+[edge]
+5.9.20.92           ansible_host=5.9.20.92 ansible_connection=local
+
+[controllers]
+controller0         ansible_host=5.9.20.92 ansible_connection=local
+;{% if mode is defined and 'HA' in mode %}
+;controller1         ansible_host=5.9.20.92 ansible_connection=local
+;{% endif %}
+
+[kafkas]
+kafka0              ansible_host=5.9.20.92 ansible_connection=local
+{% if mode is defined and 'HA' in mode %}
+kafka1              ansible_host=5.9.20.92 ansible_connection=local
+{% endif %}
+
+[zookeepers:children]
+kafkas
+
+[invokers]
+invoker0            ansible_host=144.76.27.183 ansible_connection=ssh
+invoker1            ansible_host=144.76.63.94 ansible_connection=ssh
+
+; db group is only used if db.provider is CouchDB
+[db]
+5.9.20.92         ansible_host=5.9.20.92 ansible_connection=local
+
+[redis]
+5.9.20.92          ansible_host=5.9.20.92 ansible_connection=local
+
+[apigateway]
+5.9.20.92          ansible_host=5.9.20.92 ansible_connection=local
diff --git a/openwhisk/ansible/environments/jenkins/openwhisk-vm3-he-de.j2.ini b/openwhisk/ansible/environments/jenkins/openwhisk-vm3-he-de.j2.ini
new file mode 100644
index 0000000..c150271
--- /dev/null
+++ b/openwhisk/ansible/environments/jenkins/openwhisk-vm3-he-de.j2.ini
@@ -0,0 +1,36 @@
+; the first parameter in a host is the inventory_hostname
+
+; used for local actions only
+ansible ansible_connection=local
+
+[edge]
+144.76.27.183          ansible_host=144.76.27.183 ansible_connection=local
+
+[controllers]
+controller0         ansible_host=144.76.27.183 ansible_connection=local
+;{% if mode is defined and 'HA' in mode %}
+;controller1         ansible_host=144.76.27.183 ansible_connection=local
+;{% endif %}
+
+[kafkas]
+kafka0              ansible_host=144.76.27.183 ansible_connection=local
+{% if mode is defined and 'HA' in mode %}
+kafka1              ansible_host=144.76.27.183 ansible_connection=local
+{% endif %}
+
+[zookeepers:children]
+kafkas
+
+[invokers]
+invoker0            ansible_host=144.76.63.94 ansible_connection=ssh
+invoker1            ansible_host=5.9.20.92 ansible_connection=ssh
+
+; db group is only used if db.provider is CouchDB
+[db]
+144.76.27.183         ansible_host=144.76.27.183 ansible_connection=local
+
+[redis]
+144.76.27.183          ansible_host=144.76.27.183 ansible_connection=local
+
+[apigateway]
+144.76.27.183          ansible_host=144.76.27.183 ansible_connection=local
diff --git a/openwhisk/ansible/environments/local/group_vars/all b/openwhisk/ansible/environments/local/group_vars/all
new file mode 100644
index 0000000..b8e390c
--- /dev/null
+++ b/openwhisk/ansible/environments/local/group_vars/all
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+openwhisk_tmp_dir: "{{ lookup('env', 'OPENWHISK_TMP_DIR')|default('/tmp' if ansible_distribution == 'MacOSX' else '/var/tmp', true) }}"
+config_root_dir: "{{ openwhisk_tmp_dir }}/wskconf"
+whisk_logs_dir: "{{ openwhisk_tmp_dir }}/wsklogs"
+coverage_enabled: "{{ lookup('env', 'GRADLE_COVERAGE') | default('false', true) | bool}}"
+coverage_logs_dir: "{{ openwhisk_tmp_dir }}/wskcov"
+docker_registry: ""
+docker_dns: ""
+runtimes_bypass_pull_for_local_images: true
+invoker_use_runc: "{{ ansible_distribution != 'MacOSX' }}"
+
+db_prefix: whisk_local_
+
+# API GW connection configuration
+apigw_auth_user: ""
+apigw_auth_pwd: ""
+apigw_host_v2: "http://{{ groups['apigateway']|first }}:{{apigateway.port.api}}/v2"
+
+invoker_allow_multiple_instances: true
+
+# Set kafka configuration
+kafka_heap: '512m'
+kafka_topics_completed_retentionBytes: 104857600
+kafka_topics_completed_retentionMS: 300000
+kafka_topics_health_retentionBytes: 104857600
+kafka_topics_health_retentionMS: 300000
+kafka_topics_invoker_retentionBytes: 104857600
+kafka_topics_invoker_retentionMS: 300000
+
+env_hosts_dir: "{{ playbook_dir }}/environments/local"
+
+container_pool_akka_client: true
+runtimes_enable_concurrency: true
+limit_action_concurrency_max: 500
diff --git a/openwhisk/ansible/environments/local/hosts.j2.ini b/openwhisk/ansible/environments/local/hosts.j2.ini
new file mode 100644
index 0000000..26fce32
--- /dev/null
+++ b/openwhisk/ansible/environments/local/hosts.j2.ini
@@ -0,0 +1,50 @@
+; the first parameter in a host is the inventory_hostname
+
+; used for local actions only
+ansible ansible_connection=local
+
+[edge]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[controllers]
+controller0         ansible_host=172.17.0.1 ansible_connection=local
+;{% if mode is defined and 'HA' in mode %}
+;controller1         ansible_host=172.17.0.1 ansible_connection=local
+;{% endif %}
+
+[kafkas]
+kafka0              ansible_host=172.17.0.1 ansible_connection=local
+{% if mode is defined and 'HA' in mode %}
+kafka1              ansible_host=172.17.0.1 ansible_connection=local
+{% endif %}
+
+[zookeepers:children]
+kafkas
+
+[invokers]
+invoker0            ansible_host=172.17.0.1 ansible_connection=local
+{% if mode is defined and 'HA' in mode %}
+invoker1            ansible_host=172.17.0.1 ansible_connection=local
+{% endif %}
+
+[schedulers]
+scheduler0       ansible_host=172.17.0.1 ansible_connection=local
+{% if mode is defined and 'HA' in mode %}
+scheduler1       ansible_host=172.17.0.1 ansible_connection=local
+{% endif %}
+
+; db group is only used if db.provider is CouchDB
+[db]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[elasticsearch:children]
+db
+
+[redis]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[apigateway]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[etcd]
+etcd0            ansible_host=172.17.0.1 ansible_connection=local
diff --git a/openwhisk/ansible/environments/vagrant/group_vars/all b/openwhisk/ansible/environments/vagrant/group_vars/all
new file mode 100644
index 0000000..7a332b7
--- /dev/null
+++ b/openwhisk/ansible/environments/vagrant/group_vars/all
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+openwhisk_tmp_dir: "{{ lookup('env', 'OPENWHISK_TMP_DIR')|default('/tmp', true) }}"
+config_root_dir: "{{ openwhisk_tmp_dir }}/wskconf"
+whisk_logs_dir: "{{ openwhisk_tmp_dir }}/wsklogs"
+docker_registry: ""
+docker_dns: ""
+runtimes_bypass_pull_for_local_images: true
+invoker_use_runc: "{{ ansible_distribution != 'MacOSX' }}"
+
+db_prefix: whisk_local_
+
+# API GW connection configuration
+apigw_auth_user: ""
+apigw_auth_pwd: ""
+apigw_host_v2: "http://{{ groups['apigateway']|first }}:{{apigateway.port.api}}/v2"
+
+invoker_allow_multiple_instances: true
+
+# Set kafka configuration
+kafka_heap: '512m'
+kafka_topics_completed_retentionBytes: 104857600
+kafka_topics_completed_retentionMS: 300000
+kafka_topics_health_retentionBytes: 104857600
+kafka_topics_health_retentionMS: 300000
+kafka_topics_invoker_retentionBytes: 104857600
+kafka_topics_invoker_retentionMS: 300000
+
+env_hosts_dir: "{{ playbook_dir }}/environments/local"
+
+controller_protocol: "http"
+
+cli_installation_mode: "remote"
+
+invoker_use_runc: false
diff --git a/openwhisk/ansible/environments/vagrant/hosts b/openwhisk/ansible/environments/vagrant/hosts
new file mode 100644
index 0000000..fd30391
--- /dev/null
+++ b/openwhisk/ansible/environments/vagrant/hosts
@@ -0,0 +1,32 @@
+; the first parameter in a host is the inventory_hostname
+
+; used for local actions only
+ansible ansible_connection=local
+
+[edge]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[controllers]
+controller0         ansible_host=172.17.0.1 ansible_connection=local
+;
+[kafkas]
+kafka0              ansible_host=172.17.0.1 ansible_connection=local
+
+[zookeepers:children]
+kafkas
+
+[invokers]
+invoker0            ansible_host=172.17.0.1 ansible_connection=local
+
+; db group is only used if db.provider is CouchDB
+[db]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[redis]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[apigateway]
+172.17.0.1          ansible_host=172.17.0.1 ansible_connection=local
+
+[etcd]
+etcd0            ansible_host=172.17.0.1 ansible_connection=local
diff --git a/openwhisk/ansible/etcd.yml b/openwhisk/ansible/etcd.yml
new file mode 100644
index 0000000..b167585
--- /dev/null
+++ b/openwhisk/ansible/etcd.yml
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys Openwhisk Invokers.
+
+- hosts: etcd
+  roles:
+  - etcd
diff --git a/openwhisk/ansible/files/activations_design_document_for_activations_db.json b/openwhisk/ansible/files/activations_design_document_for_activations_db.json
new file mode 100644
index 0000000..565c1e9
--- /dev/null
+++ b/openwhisk/ansible/files/activations_design_document_for_activations_db.json
@@ -0,0 +1,9 @@
+{
+  "_id": "_design/activations",
+  "views": {
+    "byDate": {
+      "map": "function (doc) {\n  if (doc.activationId !== undefined) try {\n    emit(doc.start, [doc._id, doc._rev]);\n  } catch (e) {}\n}"
+    }
+  },
+  "language": "javascript"
+}
diff --git a/openwhisk/ansible/files/auth.guest b/openwhisk/ansible/files/auth.guest
new file mode 100644
index 0000000..39eda06
--- /dev/null
+++ b/openwhisk/ansible/files/auth.guest
@@ -0,0 +1 @@
+23bc46b1-71f6-4ed5-8c54-816aa4f8c502:123zO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP
\ No newline at end of file
diff --git a/openwhisk/ansible/files/auth.whisk.system b/openwhisk/ansible/files/auth.whisk.system
new file mode 100644
index 0000000..dbcd279
--- /dev/null
+++ b/openwhisk/ansible/files/auth.whisk.system
@@ -0,0 +1 @@
+789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP
\ No newline at end of file
diff --git a/openwhisk/ansible/files/auth_design_document_for_subjects_db_v2.0.0.json b/openwhisk/ansible/files/auth_design_document_for_subjects_db_v2.0.0.json
new file mode 100644
index 0000000..bfaf7a9
--- /dev/null
+++ b/openwhisk/ansible/files/auth_design_document_for_subjects_db_v2.0.0.json
@@ -0,0 +1,10 @@
+{
+  "_id": "_design/subjects.v2.0.0",
+  "views": {
+    "identities": {
+      "map": "function (doc) {\n  if (doc.namespaces && !doc.blocked) {\n    doc.namespaces.forEach(function(namespace) {\n      var v = {_id: namespace.name + '/limits', namespace: namespace.name, uuid: namespace.uuid, key: namespace.key};\n      emit([namespace.name], v);\n      emit([namespace.uuid, namespace.key], v);\n    });\n  }\n}"
+    }
+  },
+  "language": "javascript",
+  "indexes": {}
+}
diff --git a/openwhisk/ansible/files/filter_design_document.json b/openwhisk/ansible/files/filter_design_document.json
new file mode 100644
index 0000000..f754f3a
--- /dev/null
+++ b/openwhisk/ansible/files/filter_design_document.json
@@ -0,0 +1,6 @@
+{
+  "_id": "_design/snapshotFilters",
+  "filters": {
+    "withoutDeletedAndDesignDocuments": "function(doc, req) {\n    return !doc._deleted && !(doc._id.indexOf(\"_design/\") == 0);\n}"
+  }
+}
diff --git a/openwhisk/ansible/files/genssl.sh b/openwhisk/ansible/files/genssl.sh
new file mode 100755
index 0000000..53326d6
--- /dev/null
+++ b/openwhisk/ansible/files/genssl.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+set -e
+
+PASSWORD="openwhisk"
+
+if [ "$#" -lt 3 ]; then
+    echo "usage: $0 <common name: host or ip> [server|client] <scriptdir> <OPTIONAL:TrustorePassword> <OPTIONAL:generateKey>"
+    exit
+fi
+CN=$1
+TYPE=$2
+SCRIPTDIR=$3
+export TRUSTSTORE_PASSWORD=${4:-PASSWORD}
+NAME_PREFIX=$5
+GENKEY=$6
+
+
+## generates a (self-signed) certificate
+if [[ -n $GENKEY ]]
+then
+  openssl genrsa -out "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-key.pem" 2048
+fi
+function gen_csr(){
+  echo generating server certificate request
+  openssl req -new \
+      -key "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-key.pem" \
+      -nodes \
+      -subj "/C=US/ST=NY/L=Yorktown/O=OpenWhisk/CN=$CN" \
+      -out "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-request.csr"
+}
+function gen_cert(){
+  echo generating self-signed password-less server certificate
+  openssl x509 -req \
+      -in "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-request.csr" \
+      -signkey "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-key.pem" \
+      -out "${SCRIPTDIR}/${NAME_PREFIX}openwhisk-server-cert.pem" \
+      -days 365
+}
+
+function gen_p12_keystore(){
+  openssl pkcs12 -export -name $CN \
+       -passout pass:$TRUSTSTORE_PASSWORD \
+       -in "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-cert.pem" \
+       -inkey "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-key.pem" \
+       -out "$SCRIPTDIR/${NAME_PREFIX}openwhisk-keystore.p12"
+}
+
+if [ "$TYPE" == "server_with_JKS_keystore" ]; then
+  gen_csr
+  gen_cert
+  echo generate new key and place it in the keystore
+  keytool -genkey -v \
+    -alias $CN \
+    -dname "C=US,ST=NY,L=Yorktown,O=OpenWhisk,CN=$CN" \
+    -keystore "${SCRIPTDIR}/${NAME_PREFIX}keystore.jks" \
+    -keypass:env TRUSTSTORE_PASSWORD \
+    -storepass:env TRUSTSTORE_PASSWORD \
+    -keyalg RSA \
+    -ext KeyUsage:critical="keyCertSign" \
+    -ext BasicConstraints:critical="ca:true" \
+    -validity 365
+  echo export private key from the keystore
+  keytool -keystore "${SCRIPTDIR}/${NAME_PREFIX}keystore.jks" -alias $CN -certreq -file "${SCRIPTDIR}/${NAME_PREFIX}cert-file" -storepass:env TRUSTSTORE_PASSWORD
+  echo sign the certificate with private key
+  openssl x509 -req -CA "${SCRIPTDIR}/${NAME_PREFIX}openwhisk-server-cert.pem" -CAkey "$SCRIPTDIR/${NAME_PREFIX}openwhisk-server-key.pem" -in "${SCRIPTDIR}/${NAME_PREFIX}cert-file" -out "${SCRIPTDIR}/${NAME_PREFIX}cert-signed" -days 365 -CAcreateserial -passin pass:$TRUSTSTORE_PASSWORD
+  echo import CA cert in the keystore
+  keytool -keystore "${SCRIPTDIR}/${NAME_PREFIX}keystore.jks" -alias CARoot -import -file "${SCRIPTDIR}/${NAME_PREFIX}openwhisk-server-cert.pem" -storepass:env TRUSTSTORE_PASSWORD -noprompt
+  echo import the private key in the keystore
+  keytool -keystore "${SCRIPTDIR}/${NAME_PREFIX}keystore.jks" -alias $CN -import -file "${SCRIPTDIR}/${NAME_PREFIX}cert-signed" -storepass:env TRUSTSTORE_PASSWORD -noprompt
+
+elif [ "$TYPE" == "server" ]; then
+    gen_csr
+    gen_cert
+    echo generate keystore
+    gen_p12_keystore
+elif [ "$TYPE" == "p12_keystore_only" ]; then
+    gen_csr
+    gen_p12_keystore
+else
+    echo generating client ca key
+    openssl genrsa -aes256 -passout pass:$PASSWORD -out "$SCRIPTDIR/openwhisk-client-ca-key.pem" 2048
+
+    echo generating client ca request
+    openssl req -new \
+    -key "$SCRIPTDIR/openwhisk-client-ca-key.pem" \
+    -passin pass:$PASSWORD \
+    -subj "/C=US/ST=NY/L=Yorktown/O=OpenWhisk/CN=$CN" \
+    -out "$SCRIPTDIR/openwhisk-client-ca.csr"
+
+    echo generating client ca pem
+    openssl x509 -req \
+    -in "$SCRIPTDIR/openwhisk-client-ca.csr" \
+    -signkey "$SCRIPTDIR/openwhisk-client-ca-key.pem" \
+    -passin pass:$PASSWORD \
+    -out "$SCRIPTDIR/openwhisk-client-ca-cert.pem" \
+    -days 365 -sha1 -extensions v3_ca
+
+    echo generating client key
+    openssl genrsa -aes256 -passout pass:$PASSWORD -out "$SCRIPTDIR/openwhisk-client-key.pem" 2048
+
+    echo generating client certificate csr file
+    openssl req -new \
+    -key "$SCRIPTDIR/openwhisk-client-key.pem" \
+    -passin pass:$PASSWORD \
+    -subj "/C=US/ST=NY/L=Yorktown/O=OpenWhisk/CN=guest" \
+    -out "$SCRIPTDIR/openwhisk-client-certificate-request.csr"
+
+    echo generating self-signed client certificate
+    echo 01 > $SCRIPTDIR/openwhisk-client-ca-cert.srl
+    openssl x509 -req \
+    -in "$SCRIPTDIR/openwhisk-client-certificate-request.csr" \
+    -CA "$SCRIPTDIR/openwhisk-client-ca-cert.pem" \
+    -CAkey "$SCRIPTDIR/openwhisk-client-ca-key.pem" \
+    -CAserial "$SCRIPTDIR/openwhisk-client-ca-cert.srl" \
+    -passin pass:$PASSWORD \
+    -out "$SCRIPTDIR/openwhisk-client-cert.pem" \
+    -days 365 -sha1 -extensions v3_req
+
+    echo remove client key\'s password
+    openssl rsa \
+    -in "$SCRIPTDIR/openwhisk-client-key.pem" \
+    -passin pass:$PASSWORD \
+    -out "$SCRIPTDIR/openwhisk-client-key.pem"
+fi
diff --git a/openwhisk/ansible/files/logCleanup_design_document_for_activations_db.json b/openwhisk/ansible/files/logCleanup_design_document_for_activations_db.json
new file mode 100644
index 0000000..9a97a5e
--- /dev/null
+++ b/openwhisk/ansible/files/logCleanup_design_document_for_activations_db.json
@@ -0,0 +1,9 @@
+{
+  "_id": "_design/logCleanup",
+  "views": {
+    "byDateWithLogs": {
+      "map": "function (doc) {\n  if (doc.activationId !== undefined && doc.logs && doc.logs.length > 0) try {\n    var deleteLogs = true;\n    for (i = 0; i < doc.annotations.length; i++) {\n      var a = doc.annotations[i];\n      if (a.key == \"kind\") {\n        deleteLogs = a.value != \"sequence\";\n        break;\n      }\n    }\n    if (deleteLogs) {\n      emit(doc.start, doc._id);\n    }\n  } catch (e) {}\n}"
+    }
+  },
+  "language": "javascript"
+}
diff --git a/openwhisk/ansible/files/namespace_throttlings_design_document_for_subjects_db.json b/openwhisk/ansible/files/namespace_throttlings_design_document_for_subjects_db.json
new file mode 100644
index 0000000..2346381
--- /dev/null
+++ b/openwhisk/ansible/files/namespace_throttlings_design_document_for_subjects_db.json
@@ -0,0 +1,10 @@
+{
+  "_id": "_design/namespaceThrottlings",
+  "views": {
+    "blockedNamespaces": {
+      "map": "function (doc) {\n  if (doc._id.indexOf(\"/limits\") >= 0) {\n    if (doc.concurrentInvocations === 0 || doc.invocationsPerMinute === 0) {\n      var namespace = doc._id.replace(\"/limits\", \"\");\n      emit(namespace, 1);\n    }\n  } else if (doc.subject && doc.namespaces && doc.blocked) {\n    doc.namespaces.forEach(function(namespace) {\n      emit(namespace.name, 1);\n    });\n  }\n}",
+      "reduce": "_sum"
+    }
+  },
+  "language": "javascript"
+}
diff --git a/openwhisk/ansible/files/package-versions.ini b/openwhisk/ansible/files/package-versions.ini
new file mode 100644
index 0000000..2464ea7
--- /dev/null
+++ b/openwhisk/ansible/files/package-versions.ini
@@ -0,0 +1,2 @@
+[openwhisk-cli]
+git_tag=latest
diff --git a/openwhisk/ansible/files/runtimes-nodeonly.json b/openwhisk/ansible/files/runtimes-nodeonly.json
new file mode 100644
index 0000000..dc16a1f
--- /dev/null
+++ b/openwhisk/ansible/files/runtimes-nodeonly.json
@@ -0,0 +1,46 @@
+{
+    "description": [
+        "This file describes the different languages (aka. managed action runtimes) supported by the system",
+        "as well as blackbox images that support the runtime specification.",
+        "Only actions with runtime families / kinds defined here can be created / read / updated / deleted / invoked.",
+        "Define a list of runtime families (example: 'nodejs') with at least one kind per family (example: 'nodejs:14').",
+        "Each runtime family needs a default kind (default: true).",
+        "When removing or renaming runtime families or runtime kinds from this file, preexisting actions",
+        "with the affected kinds can no longer be read / updated / deleted / invoked. In order to remove or rename",
+        "runtime families or runtime kinds, mark all affected runtime kinds as deprecated (deprecated: true) and",
+        "perform a manual migration of all affected actions.",
+        "",
+        "This file is meant to list a small set of runtimes used by Travis system tests. Using a small set of runtimes",
+        "instead of all runtimes maintained by the Apache Openwhisk community speeds up tests."
+    ],
+    "runtimes": {
+        "nodejs": [
+            {
+                "kind": "nodejs:14",
+                "default": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-nodejs-v14",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                },
+                "stemCells": [{
+                    "count": 2,
+                    "memory": "256 MB"
+                }]
+            }
+        ]
+
+    },
+    "blackboxes": [
+        {
+            "prefix": "openwhisk",
+            "name": "dockerskeleton",
+            "tag": "nightly"
+        }
+    ]
+}
diff --git a/openwhisk/ansible/files/runtimes.json b/openwhisk/ansible/files/runtimes.json
new file mode 100644
index 0000000..d61c7df
--- /dev/null
+++ b/openwhisk/ansible/files/runtimes.json
@@ -0,0 +1,268 @@
+{
+    "description": [
+        "This file describes the different languages (aka. managed action runtimes) supported by the system",
+        "as well as blackbox images that support the runtime specification.",
+        "Only actions with runtime families / kinds defined here can be created / read / updated / deleted / invoked.",
+        "Define a list of runtime families (example: 'nodejs') with at least one kind per family (example: 'nodejs:14').",
+        "Each runtime family needs a default kind (default: true).",
+        "When removing or renaming runtime families or runtime kinds from this file, preexisting actions",
+        "with the affected kinds can no longer be read / updated / deleted / invoked. In order to remove or rename",
+        "runtime families or runtime kinds, mark all affected runtime kinds as deprecated (deprecated: true) and",
+        "perform a manual migration of all affected actions.",
+        "",
+        "This file is meant to list all stable runtimes supported by the Apache Openwhisk community."
+    ],
+    "runtimes": {
+        "nodejs": [
+            {
+                "kind": "nodejs:14",
+                "default": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-nodejs-v14",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                },
+                "stemCells": [
+                    {
+                        "initialCount": 2,
+                        "memory": "256 MB",
+                        "reactive": {
+                            "minCount": 1,
+                            "maxCount": 4,
+                            "ttl": "2 minutes",
+                            "threshold": 1,
+                            "increment": 1
+                        }
+                    }
+                ]
+            },
+            {
+                "kind": "nodejs:16",
+                "default": false,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-nodejs-v16",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            }
+        ],
+        "python": [
+            {
+                "kind": "python:3",
+                "default": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-python-v3.7",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            }
+        ],
+        "swift": [
+            {
+                "kind": "swift:5.1",
+                "default": false,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-swift-v5.1",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            },
+            {
+                "kind": "swift:5.3",
+                "default": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-swift-v5.3",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            },
+            {
+                "kind": "swift:5.4",
+                "default": false,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-swift-v5.4",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            }
+        ],
+        "java": [
+            {
+                "kind": "java:8",
+                "default": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "java8action",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                },
+                "requireMain": true
+            }
+        ],
+        "php": [
+            {
+                "kind": "php:7.3",
+                "default": false,
+                "deprecated": false,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-php-v7.3",
+                    "tag": "nightly"
+                },
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            },
+            {
+                "kind": "php:7.4",
+                "default": true,
+                "deprecated": false,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-php-v7.4",
+                    "tag": "nightly"
+                },
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            },
+            {
+                "kind": "php:8.0",
+                "default": false,
+                "deprecated": false,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-php-v8.0",
+                    "tag": "nightly"
+                },
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            }
+        ],
+        "ruby": [
+            {
+                "kind": "ruby:2.5",
+                "default": true,
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                },
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-ruby-v2.5",
+                    "tag": "nightly"
+                }
+            }
+        ],
+        "go": [
+            {
+                "kind": "go:1.17",
+                "default": true,
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                },
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-golang-v1.17",
+                    "tag": "nightly"
+                }
+            }
+        ],
+        "dotnet": [
+            {
+                "kind": "dotnet:2.2",
+                "default": true,
+                "deprecated": false,
+                "requireMain": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-dotnet-v2.2",
+                    "tag": "nightly"
+                },
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            },
+            {
+                "kind": "dotnet:3.1",
+                "default": false,
+                "deprecated": false,
+                "requireMain": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-dotnet-v3.1",
+                    "tag": "nightly"
+                },
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            }
+        ],
+        "rust": [
+            {
+                "kind": "rust:1.34",
+                "default": true,
+                "image": {
+                    "prefix": "openwhisk",
+                    "name": "action-rust-v1.34",
+                    "tag": "nightly"
+                },
+                "deprecated": false,
+                "attached": {
+                    "attachmentName": "codefile",
+                    "attachmentType": "text/plain"
+                }
+            }
+        ]
+    },
+    "blackboxes": [
+        {
+            "prefix": "openwhisk",
+            "name": "dockerskeleton",
+            "tag": "nightly"
+        }
+    ]
+}
diff --git a/openwhisk/ansible/files/whisks_design_document_for_activations_db_filters_v2.1.0.json b/openwhisk/ansible/files/whisks_design_document_for_activations_db_filters_v2.1.0.json
new file mode 100644
index 0000000..9e2b1f0
--- /dev/null
+++ b/openwhisk/ansible/files/whisks_design_document_for_activations_db_filters_v2.1.0.json
@@ -0,0 +1,10 @@
+{
+  "_id": "_design/whisks-filters.v2.1.0",
+  "language": "javascript",
+  "views": {
+    "activations": {
+      "map": "function (doc) {\n  var PATHSEP = \"/\";\n  var isActivation = function (doc) { return (doc.activationId !== undefined) };\n  var summarize = function (doc) {\n    var endtime = doc.end !== 0 ? doc.end : undefined;\n    return {\n        namespace: doc.namespace,\n        name: doc.name,\n        version: doc.version,\n        publish: doc.publish,\n        annotations: doc.annotations,\n        activationId: doc.activationId,\n        start: doc.start,\n        end: endtim [...]
+      "reduce": "_count"
+    }
+  }
+}
diff --git a/openwhisk/ansible/files/whisks_design_document_for_activations_db_filters_v2.1.1.json b/openwhisk/ansible/files/whisks_design_document_for_activations_db_filters_v2.1.1.json
new file mode 100644
index 0000000..7cfed97
--- /dev/null
+++ b/openwhisk/ansible/files/whisks_design_document_for_activations_db_filters_v2.1.1.json
@@ -0,0 +1,10 @@
+{
+  "_id": "_design/whisks-filters.v2.1.1",
+  "language": "javascript",
+  "views": {
+    "activations": {
+      "map": "function (doc) {\n  var PATHSEP = \"/\";\n  var isActivation = function (doc) { return (doc.activationId !== undefined) };\n  var summarize = function (doc) {\n    var endtime = doc.end !== 0 ? doc.end : undefined;\n    return {\n        namespace: doc.namespace,\n        name: doc.name,\n        version: doc.version,\n        publish: doc.publish,\n        annotations: doc.annotations,\n        activationId: doc.activationId,\n        start: doc.start,\n        end: endtim [...]
+      "reduce": "_count"
+    }
+  }
+}
diff --git a/openwhisk/ansible/files/whisks_design_document_for_activations_db_v2.1.0.json b/openwhisk/ansible/files/whisks_design_document_for_activations_db_v2.1.0.json
new file mode 100644
index 0000000..87da6ed
--- /dev/null
+++ b/openwhisk/ansible/files/whisks_design_document_for_activations_db_v2.1.0.json
@@ -0,0 +1,10 @@
+{
+  "_id": "_design/whisks.v2.1.0",
+  "language": "javascript",
+  "views": {
+    "activations": {
+      "map": "function (doc) {\n  var PATHSEP = \"/\";\n  var isActivation = function (doc) { return (doc.activationId !== undefined) };\n  var summarize = function (doc) {\n    var endtime = doc.end !== 0 ? doc.end : undefined;\n    return {\n        namespace: doc.namespace,\n        name: doc.name,\n        version: doc.version,\n        publish: doc.publish,\n        annotations: doc.annotations,\n        activationId: doc.activationId,\n        start: doc.start,\n        end: endtim [...]
+      "reduce": "_count"
+    }
+  }
+}
diff --git a/openwhisk/ansible/files/whisks_design_document_for_entities_db_v2.1.0.json b/openwhisk/ansible/files/whisks_design_document_for_entities_db_v2.1.0.json
new file mode 100644
index 0000000..34d52f8
--- /dev/null
+++ b/openwhisk/ansible/files/whisks_design_document_for_entities_db_v2.1.0.json
@@ -0,0 +1,26 @@
+{
+  "_id": "_design/whisks.v2.1.0",
+  "language": "javascript",
+  "views": {
+    "rules": {
+      "map": "function (doc) {\n  var PATHSEP = \"/\";\n  var isRule = function (doc) {  return (doc.trigger !== undefined) };\n  if (isRule(doc)) try {\n    var ns = doc.namespace.split(PATHSEP);\n    var root = ns[0];\n    emit([doc.namespace, doc.updated], 1);\n    if (root !== doc.namespace) {\n      emit([root, doc.updated], 1);\n    }\n  } catch (e) {}\n}",
+      "reduce": "_count"
+    },
+    "packages-public": {
+      "map": "function (doc) {\n  var isPublicPackage = function(doc) { \n    return Object.keys(doc.binding).length == 0 && doc.publish;\n  }\n  if (isPublicPackage(doc)) try {\n    var value = {\n      namespace: doc.namespace,\n      name: doc.name,\n      version: doc.version,\n      publish: doc.publish,\n      annotations: doc.annotations,\n      updated: doc.updated,\n      binding: false\n    };\n    emit([doc.namespace, doc.updated], value);\n  } catch (e) {}\n}",
+      "reduce": "_count"
+    },
+    "packages": {
+      "map": "function (doc) {\n  var isPackage = function (doc) {  return (doc.binding !== undefined) };\n  if (isPackage(doc)) try {\n    var value = {\n      namespace: doc.namespace,\n      name: doc.name,\n      version: doc.version,\n      publish: doc.publish,\n      annotations: doc.annotations,\n      updated: doc.updated\n    };\n    if (Object.keys(doc.binding).length > 0) {\n      value.binding = doc.binding;\n    } else {\n      value.binding = false;\n    }\n    emit([doc.n [...]
+      "reduce": "_count"
+    },
+    "actions": {
+      "map": "function (doc) {\n  var PATHSEP = \"/\";\n  var isAction = function (doc) { return (doc.exec !== undefined) };\n  if (isAction(doc)) try {\n    var ns = doc.namespace.split(PATHSEP);\n    var root = ns[0];\n    var value = {\n      namespace: doc.namespace,\n      name: doc.name,\n      version: doc.version,\n      publish: doc.publish,\n      annotations: doc.annotations,\n      limits: doc.limits,\n      exec: { binary: doc.exec.binary || false},\n      updated: doc.updat [...]
+      "reduce": "_count"
+    },
+    "triggers": {
+      "map": "function (doc) {\n  var PATHSEP = \"/\";\n  var isTrigger = function (doc) { return (doc.exec === undefined && doc.binding === undefined && doc.parameters !== undefined) };\n  if (isTrigger(doc)) try {\n    var ns = doc.namespace.split(PATHSEP);\n    var root = ns[0];\n    var value = {\n      namespace: doc.namespace,\n      name: doc.name,\n      version: doc.version,\n      publish: doc.publish,\n      annotations: doc.annotations,\n      updated: doc.updated\n    };\n   [...]
+      "reduce": "_count"
+    }
+  }
+}
\ No newline at end of file
diff --git a/openwhisk/ansible/group_vars/all b/openwhisk/ansible/group_vars/all
new file mode 100644
index 0000000..eee2f52
--- /dev/null
+++ b/openwhisk/ansible/group_vars/all
@@ -0,0 +1,553 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+mode: deploy
+lean: false
+prompt_user: true
+openwhisk_home: "{{ lookup('env', 'OPENWHISK_HOME') | default(playbook_dir ~ '/..', true) }}"
+openwhisk_cli_home: "{{ lookup('env', 'OPENWHISK_CLI') | default(openwhisk_home ~ '/../openwhisk-cli', true) }}"
+exclude_logs_from: []
+
+# This whisk_api_localhost_name_default is used to configure nginx to permit vanity URLs for web actions
+# for local deployment. For a public deployment, the specific environment group vars should define
+# whisk_api_host_name; this is available to actions and hence must resolve from inside an action container
+# specific to the deployment (i.e., it may be an IP address rather than a hostname in some cases).
+# For a local deployment, use whisk_api_localhost_name. For a deployment which requires
+# different name resolution between the whisk_api_host_name and the whisk_api_local_host_name, both should
+# be defined so that the nginx configuration for the server name reflects the public facing naming (of the
+# edge router) even if it is different from the API host available to the actions. The precedence order for
+# configuring nginx and the SSL certificate generation is:
+#   whisk_api_localhost_name (first)
+#   whisk_api_host_name (second)
+#   whisk_api_localhost_name_default (last)
+whisk_api_localhost_name_default: "localhost"
+
+# Type of your environment.
+# If you want to deploy everything on your local machine use 'local'.
+# If you use a docker-machine on a mac use 'docker-machine'
+# If you want to deploy Openwhisk to other machines use 'distributed'
+environmentInformation:
+  type: "{{ environment_type | default('local') }}"
+
+hosts_dir: "{{ inventory_dir | default(env_hosts_dir) }}"
+
+whisk:
+  version:
+    date: "{{ansible_date_time.iso8601}}"
+  feature_flags:
+    require_api_key_annotation: "{{ require_api_key_annotation | default(true) | lower }}"
+    require_response_payload: "{{ require_response_payload | default(true) | lower }}"
+  cluster_name: "{{ whisk_cluster_name | default('whisk') }}"
+  containerProxy:
+    timeouts:
+      idleContainer: "{{ containerProxy_timeouts_idleContainer | default('10 minutes') }}"
+      pauseGrace: "{{ containerProxy_timeouts_pauseGrace | default('10 seconds') }}"
+      keepingDuration: "{{ containerProxy_timeouts_keepingDuration | default('10 minutes') }}"
+
+##
+# configuration parameters related to support runtimes (see org.apache.openwhisk.core.entity.ExecManifest for schema of the manifest).
+# briefly the parameters are:
+#
+#   runtimes_registry: optional registry (with trailing slash) where to pull docker images from for default runtimes (in manifest)
+#   user_images_registry: optional registry (with trailing slash) where to pull docker images from for blackbox images
+#
+#   skip_pull_runtimes: this will skip pulling the images to the invoker (images must exist there somehow)
+#
+#   runtimes_manifest: set of language runtime families grouped by language (e.g., nodejs, python) and blackbox images to pre-pull
+#
+#   runtimes_bypass_pull_for_local_images: optional, if true, allow images with a prefix that matches
+#       {{ runtimes_local_image_prefix }} to skip docker pull in invoker even if the image is not part of the blackbox set
+#
+
+manifestfile: "{{ manifest_file | default('/ansible/files/runtimes.json') }}"
+runtimesManifest: "{{ runtimes_manifest | default(lookup('file', openwhisk_home ~ '{{ manifestfile }}') | from_json) }}"
+
+limits:
+  invocationsPerMinute: "{{ limit_invocations_per_minute | default(60) }}"
+  concurrentInvocations: "{{ limit_invocations_concurrent | default(30) }}"
+  firesPerMinute: "{{ limit_fires_per_minute | default(60) }}"
+  sequenceMaxLength: "{{ limit_sequence_max_length | default(50) }}"
+
+# Moved here to avoid recursions. Please do not use outside of controller-dict.
+__controller_ssl_keyPrefix: "controller-"
+__controller_blackbox_fraction: 0.10
+
+# port means outer port
+controller:
+  dir:
+    become: "{{ controller_dir_become | default(false) }}"
+  confdir: "{{ config_root_dir }}/controller"
+  basePort: 10001
+  heap: "{{ controller_heap | default('2g') }}"
+  arguments: "{{ controller_arguments | default('') }}"
+  managedFraction: "{{ controller_managed_fraction | default(1.0 - (controller_blackbox_fraction | default(__controller_blackbox_fraction))) }}"
+  blackboxFraction: "{{ controller_blackbox_fraction | default(__controller_blackbox_fraction) }}"
+  timeoutFactor: "{{ controller_timeout_factor | default(2) }}"
+  timeoutAddon: "{{ controller_timeout_addon | default('1 m') }}"
+  instances: "{{ groups['controllers'] | length }}"
+  akka:
+    provider: cluster
+    cluster:
+      basePort: 8000
+      host: "{{ groups['controllers'] | map('extract', hostvars, 'ansible_host') | list }}"
+      bindPort: 2551
+      # at this moment all controllers are seed nodes
+      seedNodes: "{{ groups['controllers'] | map('extract', hostvars, 'ansible_host') | list }}"
+  loadbalancer:
+    spi: "{{ controller_loadbalancer_spi | default('') }}"
+  authentication:
+    spi: "{{ controller_authentication_spi | default('') }}"
+  loglevel: "{{ controller_loglevel | default(whisk_loglevel) | default('INFO') }}"
+  username: "{{ controller_username | default('controller.user') }}"
+  password: "{{ controller_password | default('controller.pass') }}"
+  entitlement:
+    spi: "{{ controller_entitlement_spi | default('') }}"
+  protocol: "{{ controller_protocol | default('https') }}"
+  ssl:
+    cn: openwhisk-controllers
+    keyPrefix: "{{ __controller_ssl_keyPrefix }}"
+    storeFlavor: PKCS12
+    clientAuth: "{{ controller_client_auth | default('true') }}"
+    cert: "{{ __controller_ssl_keyPrefix }}openwhisk-server-cert.pem"
+    key: "{{ __controller_ssl_keyPrefix }}openwhisk-server-key.pem"
+    keystore:
+      password: "openwhisk"
+      name: "{{ __controller_ssl_keyPrefix }}openwhisk-keystore.p12"
+  extraEnv: "{{ controller_extraEnv | default({}) }}"
+  deployment:
+    ignore_error: "{{ controller_deployment_ignore_error | default('False') }}"
+    retries: "{{ controller_deployment_retries | default(180) }}"
+    delay: "{{ controller_deployment_delay | default(5) }}"
+
+jmx:
+  basePortController: 15000
+  rmiBasePortController: 16000
+  basePortInvoker: 17000
+  rmiBasePortInvoker: 18000
+  basePortScheduler: 21000
+  rmiBasePortScheduler: 22000
+  user: "{{ jmxuser | default('jmxuser') }}"
+  pass: "{{ jmxuser | default('jmxpass') }}"
+  jvmCommonArgs: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=/home/owuser/jmxremote.password -Dcom.sun.management.jmxremote.access.file=/home/owuser/jmxremote.access"
+  enabled: "{{ jmxremote_enabled | default('true') }}"
+
+transactions:
+  header: "{{ transactions_header | default('X-Request-ID') }}"
+
+registry:
+  confdir: "{{ config_root_dir }}/registry"
+
+kafka:
+  topicsPrefix: "{{ kafka_topics_prefix | default('') }}"
+  topicsUserEventPrefix: "{{ kafka_topics_userEvent_prefix | default(kafka_topics_prefix) | default('') }}"
+  ssl:
+    client_authentication: required
+    keystore:
+      name: kafka-keystore.jks
+      password: openwhisk
+    cipher_suites:
+    - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
+    - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
+    - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+    protocols:
+    - TLSv1.2
+  protocol: "{{ kafka_protocol_for_setup }}"
+  version: "{{ kafka_version | default('2.12-2.3.1') }}"
+  port: 9072
+  advertisedPort: 9093
+  ras:
+    port: 8093
+  heap: "{{ kafka_heap | default('1g') }}"
+  replicationFactor: "{{ kafka_replicationFactor | default((groups['kafkas']|length)|int) }}"
+  offsetsTopicReplicationFactor: "{{ kafka_offsetsTopicReplicationFactor | default(kafka_replicationFactor) | default((groups['kafkas']|length)|int) }}"
+  # adapt this param for production deployments depending on the number of kafka consumers
+  networkThreads: "{{ kafka_network_threads | default(3) }}"
+
+kafka_connect_string: "{% set ret = [] %}\
+                       {% for host in groups['kafkas'] %}\
+                         {{ ret.append( hostvars[host].ansible_host + ':' + ((kafka.advertisedPort+loop.index-1)|string) ) }}\
+                       {% endfor %}\
+                       {{ ret | join(',') }}"
+
+kafka_protocol_for_setup: "{{ kafka_protocol | default('PLAINTEXT') }}"
+
+zookeeper:
+  version: 3.4.14
+  port: 2181
+
+zookeeper_connect_string: "{% set ret = [] %}\
+                           {% for host in groups['zookeepers'] %}\
+                             {{ ret.append( hostvars[host].ansible_host + ':' + ((zookeeper.port+loop.index-1)|string) ) }}\
+                           {% endfor %}\
+                           {{ ret | join(',') }}"
+
+invokerHostnameFromMap: "{{ groups['invokers'] | map('extract', hostvars, 'ansible_host') | list | first }}"
+invokerHostname: "{{ invokerHostnameFromMap | default(inventory_hostname) }}"
+
+# Moved here to avoid recursions. Please do not use outside of invoker-dict.
+__invoker_ssl_keyPrefix: "invoker-"
+
+invoker:
+  dir:
+    become: "{{ invoker_dir_become | default(false) }}"
+  confdir: "{{ config_root_dir }}/invoker"
+  port: 12001
+  heap: "{{ invoker_heap | default('2g') }}"
+  arguments: "{{ invoker_arguments | default('') }}"
+  userMemory: "{{ invoker_user_memory | default('2048m') }}"
+  # Specify if it is allowed to deploy more than 1 invoker on a single machine.
+  allowMultipleInstances: "{{ invoker_allow_multiple_instances | default(false) }}"
+  # Specify if it should use docker-runc or docker to pause/unpause containers
+  useRunc: "{{ invoker_use_runc | default(true) }}"
+  docker:
+    become: "{{ invoker_docker_become | default(false) }}"
+    runcdir: "{{ invoker_runcdir | default('/run/docker/runtime-runc/moby') }}"
+    volumes: "{{ invoker_docker_volumes | default([]) }}"
+  loglevel: "{{ invoker_loglevel | default(whisk_loglevel) | default('INFO') }}"
+  jmxremote:
+    jvmArgs: "{% if inventory_hostname in groups['invokers'] %}
+    {{ jmx.jvmCommonArgs }} -Djava.rmi.server.hostname={{ invokerHostname }} -Dcom.sun.management.jmxremote.rmi.port={{ jmx.rmiBasePortInvoker + groups['invokers'].index(inventory_hostname) }} -Dcom.sun.management.jmxremote.port={{ jmx.basePortInvoker + groups['invokers'].index(inventory_hostname) }}
+    {% endif %}"
+  extraEnv: "{{ invoker_extraEnv | default({}) }}"
+  protocol: "{{ invoker_protocol | default('https') }}"
+  username: "{{ invoker_username | default('invoker.user') }}"
+  password: "{{ invoker_password | default('invoker.pass') }}"
+  ssl:
+    cn: "openwhisk-invokers"
+    keyPrefix: "{{ __invoker_ssl_keyPrefix }}"
+    storeFlavor: "PKCS12"
+    clientAuth: "{{ invoker_client_auth | default('true') }}"
+    cert: "{{ __invoker_ssl_keyPrefix }}openwhisk-server-cert.pem"
+    key: "{{ __invoker_ssl_keyPrefix }}openwhisk-server-key.pem"
+    keystore:
+      password: "{{ invoker_keystore_password | default('openwhisk') }}"
+      name: "{{ __invoker_ssl_keyPrefix }}openwhisk-keystore.p12"
+  container:
+    creationMaxPeek: "{{ container_creation_max_peek | default(500) }}"
+  reactiveSpi: "{{ invokerReactive_spi | default('') }}"
+  serverSpi: "{{ invokerServer_spi | default('') }}"
+  deployment:
+    ignore_error: "{{ invoker_deployment_ignore_error | default('False') }}"
+    retries: "{{ invoker_deployment_retries | default(180) }}"
+    delay: "{{ invoker_deployment_delay | default(5) }}"
+
+userLogs:
+  spi: "{{ userLogs_spi | default('org.apache.openwhisk.core.containerpool.logging.DockerToActivationLogStoreProvider') }}"
+
+nginx:
+  confdir: "{{ config_root_dir }}/nginx"
+  htmldir: "{{ ui_path | default(false) }}"
+  dir:
+    become: "{{ nginx_dir_become | default(false) }}"
+  version: "{{ nginx_version | default('1.21.1') }}"
+  port:
+    http: 80
+    https: 443
+  ssl:
+    path: "{{ nginx_ssl_path | default(playbook_dir +'/roles/nginx/files') }}"
+    cert: "{{ nginx_ssl_server_cert | default('openwhisk-server-cert.pem') }}"
+    key: "{{ nginx_ssl_server_key | default('openwhisk-server-key.pem') }}"
+    client_ca_cert: "{{ nginx_ssl_client_ca_cert | default('openwhisk-client-ca-cert.pem') }}"
+    verify_client: "{{ nginx_ssl_verify_client | default('off') }}"
+    password_file: "{{ nginx_ssl_password_file | default(false) }}"
+  wpn:
+    router: "{{ nginx_wpn_router | default('1') }}"
+  special_users: "{{ nginx_special_users | default('[]') }}"
+
+# These are the variables to define all database relevant settings.
+# The authKeys are the users, that are initially created to use OpenWhisk.
+# The keys are stored in ansible/files and will be inserted into the authentication databse.
+# The key db.whisk.actions is the name of the database where all artifacts of the user are stored. These artifacts are actions, triggers, rules and packages.
+# The key db.whisk.activation is the name of the database where all activations are stored.
+# The key db.whisk.auth is the name of the authentication database where all keys of all users are stored.
+# The db_prefix is defined for each environment on its own. The CouchDb credentials are also defined for each environment on its own.
+db:
+  provider: "{{ db_provider | default(lookup('ini', 'db_provider section=db_creds file={{ playbook_dir }}/db_local.ini')) }}"
+  protocol: "{{ db_protocol | default(lookup('ini', 'db_protocol section=db_creds file={{ playbook_dir }}/db_local.ini')) }}"
+  port: "{{ db_port | default(lookup('ini', 'db_port section=db_creds file={{ playbook_dir }}/db_local.ini')) }}"
+  host: "{{ db_host | default(lookup('ini', 'db_host section=db_creds file={{ playbook_dir }}/db_local.ini')) }}"
+  persist_path: "{{ db_persist_path | default(false) }}"
+  instances: "{{ groups['db'] | length }}"
+  authkeys:
+  - guest
+  - whisk.system
+  whisk:
+    actions: "{{ db_prefix }}whisks"
+    activations: "{{ db_prefix }}activations"
+    auth: "{{ db_prefix }}subjects"
+  credentials:
+    admin:
+      user: "{{ db_username | default(lookup('ini', 'db_username section=db_creds file={{ playbook_dir }}/db_local.ini')) }}"
+      pass: "{{ db_password | default(lookup('ini', 'db_password section=db_creds file={{ playbook_dir }}/db_local.ini')) }}"
+    controller:
+      user: "{{ db_controller_user | default(lookup('ini', 'db_username section=controller file={{ playbook_dir }}/db_local.ini')) }}"
+      pass: "{{ db_controller_pass | default(lookup('ini', 'db_password section=controller file={{ playbook_dir }}/db_local.ini')) }}"
+    invoker:
+      user: "{{ db_invoker_user | default(lookup('ini', 'db_username section=invoker file={{ playbook_dir }}/db_local.ini')) }}"
+      pass: "{{ db_invoker_pass | default(lookup('ini', 'db_password section=invoker file={{ playbook_dir }}/db_local.ini')) }}"
+    scheduler:
+      user: "{{ db_scheduler_user | default(lookup('ini', 'db_username section=scheduler file={{ playbook_dir }}/db_local.ini')) }}"
+      pass: "{{ db_scheduler_pass | default(lookup('ini', 'db_password section=scheduler file={{ playbook_dir }}/db_local.ini')) }}"
+  artifact_store:
+    backend: "{{ db_artifact_backend | default('CouchDB') }}"
+  activation_store:
+    backend: "{{ db_activation_backend | default('CouchDB') }}"
+  elasticsearch:
+    protocol: "{{ elastic_protocol | default('http') }}"
+    port: "{{ elastic_port | default(9200) }}"
+    index_pattern: "{{ elastic_index_pattern | default('openwhisk-%s') }}"
+    base_transport_port: "{{ elastic_transport_port_base | default(9300) }}"
+    confdir: "{{ config_root_dir }}/elasticsearch"
+    dir:
+      become: "{{ elastic_dir_become | default(false) }}"
+    base_volume: "{{ elastic_base_volume | default('esdata') }}"
+    cluster_name: "{{ elastic_cluster_name | default('openwhisk') }}"
+    java_opts: "{{ elastic_java_opts | default('-Xms1g -Xmx1g') }}"
+    loglevel: "{{ elastic_loglevel | default('INFO') }}"
+    # the user id of elasticsearch process, default is 1000, if you have enabled user namespace
+    # for docker daemon, this need to be changed correspondingly
+    uid: "{{ elastic_uid | default(1000) }}"
+    auth:
+      admin:
+        username: "{{ elastic_username | default('admin') }}"
+        password: "{{ elastic_password | default('admin') }}"
+  mongodb:
+    connect_string: "{{ mongodb_connect_string | default('mongodb://172.17.0.1:27017') }}"
+    database: "{{ mongodb_database | default('whisks') }}"
+    data_volume: "{{ mongodb_data_volume | default('mongo-data') }}"
+
+apigateway:
+  port:
+    api: 9000
+    mgmt: 9001
+  # Default to 'nightly', which tracks the head revision of the master branch of apigateway's gitrepo
+  version: nightly
+
+redis:
+  version: 4.0
+  port: 6379
+  password: openwhisk
+
+linux:
+  version: 4.4.0-31
+
+couchdb:
+  version: 2.3
+
+elasticsearch:
+  version: 6.7.2
+
+elasticsearch_connect_string: "{% set ret = [] %}\
+                               {% for host in groups['elasticsearch'] %}\
+                               {{ ret.append( hostvars[host].ansible_host + ':' + ((db.elasticsearch.port|int+loop.index-1)|string) ) }}\
+                               {% endfor %}\
+                               {{ ret | join(',') }}"
+mongodb:
+  version: 4.4.0
+
+docker:
+  # The user to install docker for. Defaults to the ansible user if not set. This will be the user who is able to run
+  # docker commands on a machine setup with prereq_build.yml
+  #user:
+  image:
+    prefix: "{{ docker_image_prefix | default('whisk') }}"
+    tag: "{{ docker_image_tag | default('latest') }}"
+  version: 1.12.0-0~trusty
+  storagedriver: overlay
+  port: 4243
+  restart:
+    policy: always
+  pull:
+    retries: 10
+    delay: 10
+  timezone: "{{ docker_timezone | default('UTC') }}"
+
+cli:
+  path: "{{ openwhisk_home }}/bin/wsk"
+
+# The default name space is /whisk.system. The catalog namespace must begin with a slash "/".
+catalog_namespace: "/whisk.system"
+
+# The catalog_auth_key is used to determine the secret key to authenticate the openwhisk service.
+# The value for this variable can be set to either the secret key itself or the file, which
+# saves the secret key.
+# By default, we take the key from ansible/files/auth.whisk.system.
+catalog_auth_key: "{{ playbook_dir }}/files/auth.whisk.system"
+
+# The catalog_repos is used to specify all the catalog names and repository URLs,
+# so that openwhisk knows where to download the catalog and install them. The key
+# specifies the catalog name and the url saves the URL of the repository. The location
+# specifies the location to save the code of the catalog. The version specifies the hash
+# of the commit to be cloned. If it is omit or set to HEAD, the latest commit will be
+# selected. The repo_update specifies whether to retrieve new revisions from the origin
+# repository and the default value is yes, meaning that it will retrieve the new
+# revisions. The keys url and location are mandatory and the keys version and repo_update
+# are optional. To add a new repository, please follow the template by adding:
+#
+# catalog_repos:
+#   ...
+#   <catalog-name>:
+#     url: <URL of repository>, mandatory.
+#     location: <local location to save the catalog>, mandatory.
+#     version: <hash of the commit>, optional, default to HEAD.
+#     repo_update: <whether to retrieve new revisions from the origin repository>,
+#                  optional, default to no. Yes means to retrieve the new revisions, and
+#                  no means not to retrieve the new revisions.
+#
+catalog_repos:
+  openwhisk-catalog:
+    url: https://github.com/apache/openwhisk-catalog.git
+    # Set the local location as the same level as openwhisk home, but it can be changed.
+    location: "{{ openwhisk_home }}/../openwhisk-catalog"
+    version: "HEAD"
+    repo_update: "no"
+
+# The openwhisk_cli is used to determine how to install the OpenWhisk CLI. The
+# installation_mode can be specified into two modes: remote and local.
+# The mode remote means to download the available binaries from the releases page
+# of the official openwhisk cli repository. The mode local means to build the binaries
+# locally in a directory and get them from the local directory. The default value
+# for openwhisk is local.
+#
+# The name specifies the package name of the binaries in remote mode.
+#
+# The dest_name specifies the package name of the binaries in Nginx in remote mode.
+#
+# The location specifies the official website where Openwhisk CLI is hosted in
+# remote mode or location to save the binaries of the OpenWhisk CLI in local mode.
+
+openwhisk_cli_tag: "{{ cli_tag | default(lookup('ini', 'git_tag section=openwhisk-cli file=' ~ openwhisk_home ~ '/ansible/files/package-versions.ini')) }}"
+openwhisk_cli:
+  installation_mode: "{{ cli_installation_mode | default(lookup('env', 'OPENWHISK_CLI_MODE')) | default('remote', true) }}"
+  archive_name: OpenWhisk_CLI
+  nginxdir:
+    name: "{{ nginx.confdir }}/cli/go/download"
+    become: "{{ cli_dir_become | default(false) }}"
+  local:
+    location: "{{ openwhisk_cli_home }}/build"
+  remote:
+    location: "https://github.com/apache/openwhisk-cli/releases/download/{{ openwhisk_cli_tag }}"
+
+# Controls access to log directories
+logs:
+  dir:
+    become: "{{ logs_dir_become | default(false) }}"
+
+# Metrics Configuration
+metrics:
+  log:
+    enabled: "{{ metrics_log | default(true) }}"
+  kamon:
+    enabled: "{{ metrics_kamon | default(false) }}"
+    tags: "{{ metrics_kamon_tags | default(false) }}"
+    host: "{{ metrics_kamon_statsd_host | default('') }}"
+    port: "{{ metrics_kamon_statsd_port | default('8125') }}"
+
+user_events: "{{ user_events_enabled | default(false) | lower }}"
+
+zeroDowntimeDeployment:
+  enabled: "{{ zerodowntime_deployment_switch | default(false) }}"
+
+etcd:
+  version: "{{ etcd_version | default('v3.4.0') }}"
+  client:
+    port: 2379
+  server:
+    port: 2480
+  cluster:
+    token: "{{ etcd_cluster_token | default('openwhisk-etcd-token') }}"
+  dir:
+    data: "{{ etcd_data_dir | default('') }}"
+  lease:
+    timeout: "{{ etcd_lease_timeout | default(10) }}"
+  loglevel: "{{ etcd_log_level | default('info') }}"
+  quota_backend_bytes: "{{ etcd_quota_backend_bytes | default(2147483648) }}" # 2GB
+  snapshot_count: "{{ etcd_snapshot_count | default(10000) }}"
+  auto_compaction_retention: "{{ etcd_auto_compaction_retention | default('10m') }}"
+  auto_compaction_mode: "{{ etcd_auto_compaction_mode | default('periodic') }}"
+  pool_threads: "{{ etcd_pool_threads | default(10) }}"
+
+etcd_connect_string: "{% set ret = [] %}\
+                      {% for host in groups['etcd'] %}\
+                        {{ ret.append( hostvars[host].ansible_host + ':' + ((etcd.client.port+loop.index-1)|string) ) }}\
+                      {% endfor %}\
+                      {{ ret | join(',') }}"
+
+
+__scheduler_blackbox_fraction: 0.10
+
+watcher:
+    eventNotificationDelayMs: "{{ watcher_notification_delay | default('5000 ms')  }}"
+
+durationChecker:
+    spi: "{{ duration_checker_spi | default('') }}"
+    timeWindow: "{{ duration_checker_time_window | default('1 d') }}"
+
+enable_scheduler: "{{ scheduler_enable | default(false) }}"
+
+scheduler:
+  protocol: "{{ scheduler_protocol | default('http') }}"
+  dir:
+    become: "{{ scheduler_dir_become | default(false) }}"
+  confdir: "{{ config_root_dir }}/scheduler"
+  basePort: 14001
+  grpc:
+    basePort: 13001
+    tls: "{{ scheduler_grpc_tls | default(false) }}"
+  maxPeek: "{{ scheduler_max_peek | default(128) }}"
+  heap: "{{ scheduler_heap | default('2g') }}"
+  arguments: "{{ scheduler_arguments | default('') }}"
+  instances: "{{ groups['schedulers'] | length }}"
+  username: "{{ scheduler_username | default('scheduler.user') }}"
+  password: "{{ scheduler_password | default('scheduler.pass') }}"
+  akka:
+    provider: cluster
+    cluster:
+      basePort: 25520
+      host: "{{ groups['schedulers'] | map('extract', hostvars, 'ansible_host') | list }}"
+      bindPort: 3551
+      # at this moment all schedulers are seed nodes
+      seedNodes: "{{ groups['schedulers'] | map('extract', hostvars, 'ansible_host') | list }}"
+  loglevel: "{{ scheduler_loglevel | default(whisk_loglevel) | default('INFO') }}"
+  extraEnv: "{{ scheduler_extraEnv | default({}) }}"
+  dataManagementService:
+    retryInterval: "{{ scheduler_dataManagementService_retryInterval | default('1 second') }}"
+  inProgressJobRetention: "{{ scheduler_inProgressJobRetention | default('20 seconds') }}"
+  blackboxMultiple: "{{ scheduler_blackboxMultiple | default(15) }}"
+  managedFraction: "{{ scheduler_managed_fraction | default(1.0 - (scheduler_blackbox_fraction | default(__scheduler_blackbox_fraction))) }}"
+  blackboxFraction: "{{ scheduler_blackbox_fraction | default(__scheduler_blackbox_fraction) }}"
+  scheduling:
+    staleThreshold: "{{ scheduler_scheduling_staleThreshold | default('100 milliseconds') }}"
+    checkInterval: "{{ scheduler_scheduling_checkInterval | default('100 milliseconds') }}"
+    dropInterval: "{{ scheduler_scheduling_dropInterval | default('10 seconds') }}"
+  queueManager:
+    maxSchedulingTime: "{{ scheduler_maxSchedulingTime | default('20 second') }}"
+    maxRetriesToGetQueue: "{{ scheduler_maxRetriesToGetQueue | default(13) }}"
+  queue:
+    # the queue's state Running timeout, e.g. if have no activation comes into queue when Running, the queue state will be changed from Running to Idle and delete the decision algorithm actor
+    idleGrace: "{{ scheduler_queue_idleGrace | default('10 minutes') }}"
+    # the queue's state Idle timeout, e.g. if have no activation comes into queue when Idle, the queue state will be changed from Idle to Removed
+    stopGrace: "{{ scheduler_queue_stopGrace | default('10 minutes') }}"
+    # the queue's state Paused timeout, e.g. if have no activation comes into queue when Paused, the queue state will be changed from Paused to Removed
+    flushGrace: "{{ scheduler_queue_flushGrace | default('60 seconds') }}"
+    gracefulShutdownTimeout: "{{ scheduler_queue_gracefulShutdownTimeout | default('5 seconds') }}"
+    maxRetentionSize: "{{ scheduler_queue_maxRetentionSize | default(10000) }}"
+    maxRetentionMs: "{{ scheduler_queue_maxRetentionMs | default(60000) }}"
+    maxBlackboxRetentionMs: "{{ scheduler_queue_maxBlackboxRetentionMs | default(300000) }}"
+    throttlingFraction: "{{ scheduler_queue_throttlingFraction | default(0.9) }}"
+    durationBufferSize: "{{ scheduler_queue_durationBufferSize | default(10) }}"
+  deployment_ignore_error: "{{ scheduler_deployment_ignore_error | default('False') }}"
diff --git a/openwhisk/ansible/initMongoDB.yml b/openwhisk/ansible/initMongoDB.yml
new file mode 100644
index 0000000..58672a6
--- /dev/null
+++ b/openwhisk/ansible/initMongoDB.yml
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook will initialize the immortal DBs in the database account.
+# This step is usually done only once per deployment.
+
+- hosts: ansible
+  tasks:
+  - name: create necessary auth keys
+    mongodb:
+      connect_string: "{{ db.mongodb.connect_string }}"
+      database: "{{ db.mongodb.database }}"
+      collection: "whiskauth"
+      doc:
+        _id: "{{ item }}"
+        subject: "{{ item }}"
+        namespaces:
+          - name: "{{ item }}"
+            uuid: "{{ key.split(':')[0] }}"
+            key: "{{ key.split(':')[1] }}"
+      mode: "doc"
+      force_update: True
+    vars:
+      key: "{{ lookup('file', 'files/auth.{{ item }}') }}"
+    with_items: "{{ db.authkeys }}"
diff --git a/openwhisk/ansible/initdb.yml b/openwhisk/ansible/initdb.yml
new file mode 100644
index 0000000..4656ca8
--- /dev/null
+++ b/openwhisk/ansible/initdb.yml
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook will initialize the immortal DBs in the database account.
+# This step is usually done only once per deployment.
+
+- hosts: ansible
+  tasks:
+  - include_tasks: tasks/initdb.yml
diff --git a/openwhisk/ansible/invoker.yml b/openwhisk/ansible/invoker.yml
new file mode 100644
index 0000000..9a6aca8
--- /dev/null
+++ b/openwhisk/ansible/invoker.yml
@@ -0,0 +1,42 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys Openwhisk Invokers.
+
+- hosts: invokers
+  vars:
+    #
+    # host_group - usually "{{ groups['...'] }}" where '...' is what was used
+    #   for 'hosts' above.  The hostname of each host will be looked up in this
+    #   group to assign a zero-based index.  That index will be used in concert
+    #   with 'name_prefix' below to assign a host/container name.
+    host_group: "{{ groups['invokers'] }}"
+    #
+    # name_prefix - a unique prefix for this set of invokers.  The prefix
+    #   will be used in combination with an index (determined using
+    #   'host_group' above) to name host/invokers.
+    name_prefix: "invoker"
+    #
+    # invoker_index_base - the deployment process allocates host docker
+    #   ports to individual invokers based on their indices.  This is an
+    #   additional offset to prevent collisions between different invoker
+    #   groups. Usually 0 if only one group is being deployed, otherwise
+    #   something like "{{ groups['firstinvokergroup']|length }}"
+    invoker_index_base: 0
+
+  roles:
+    - invoker
diff --git a/openwhisk/ansible/kafka.yml b/openwhisk/ansible/kafka.yml
new file mode 100644
index 0000000..cb81159
--- /dev/null
+++ b/openwhisk/ansible/kafka.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys an Openwhisk Kafka bus.
+
+- hosts: zookeepers
+  roles:
+  - zookeeper
+
+- hosts: kafkas
+  roles:
+  - kafka
diff --git a/openwhisk/ansible/library/mongodb.py b/openwhisk/ansible/library/mongodb.py
new file mode 100644
index 0000000..fb610fa
--- /dev/null
+++ b/openwhisk/ansible/library/mongodb.py
@@ -0,0 +1,283 @@
+#!/usr/bin/python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+---
+module: mongodb
+short_description:  A module which support some simple operations on MongoDB.
+description:
+    - Including add user/insert document/create indexes in MongoDB
+options:
+    connect_string:
+        description:
+            - The uri of mongodb server
+        required: true
+    database:
+        description:
+            - The name of the database you want to manipulate
+        required: true
+    user:
+        description:
+            - The name of the user to add or remove, required when use 'user' mode
+        required: false
+        default: null
+    password:
+        description:
+            - The password to use for the user, required when use 'user' mode
+        required: false
+        default: null
+    roles:
+        description:
+            - The roles of the user, it's a list of dict, each dict requires two fields: 'db' and 'role', required when use 'user' mode
+        required: false
+        default: null
+    collection:
+        required: false
+        description:
+            - The name of the collection you want to manipulate, required when use 'doc' or 'indexes' mode
+    doc:
+        required: false
+        description:
+            - The document you want to insert into MongoDB, required when use 'doc' mode
+    indexes:
+        required: false
+        description:
+            - The indexes you want to create in MongoDB, it's a list of dict, you can see the example for the usage, required when use 'index' mode
+    force_update:
+        required: false
+        description:
+            - Whether replace/update existing user or doc or raise DuplicateKeyError, default is false
+    mode:
+        required: false
+        default: user
+        choices: ['user', 'doc', 'index']
+        description:
+            - use 'user' mode if you want to add user, 'doc' mode to insert document, 'index' mode to create indexes
+
+requirements: [ "pymongo" ]
+author:
+    - "Jinag PengCheng"
+'''
+
+EXAMPLES = '''
+# add user
+- mongodb:
+    connect_string: mongodb://localhost:27017
+    database: admin
+    user: test
+    password: 123456
+    roles:
+      - db: test_database
+        role: read
+    force_update: true
+
+# add doc
+- mongodb:
+    connect_string: mongodb://localhost:27017
+    mode: doc
+    database: admin
+    collection: main
+    doc:
+      id: "id/document"
+      title: "the name of document"
+      content: "which doesn't matter"
+    force_update: true
+
+# add indexes
+- mongodb:
+    connect_string: mongodb://localhost:27017
+    mode: index
+    database: admin
+    collection: main
+    indexes:
+      - index:
+        - field: updated_at
+          direction: 1
+        - field: name
+          direction: -1
+        name: test-index
+        unique: true
+'''
+
+import traceback
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+
+try:
+    from pymongo import ASCENDING, DESCENDING, GEO2D, GEOHAYSTACK, GEOSPHERE, HASHED, TEXT
+    from pymongo import IndexModel
+    from pymongo import MongoClient
+    from pymongo.errors import DuplicateKeyError
+except ImportError:
+    pass
+
+
+# =========================================
+# MongoDB module specific support methods.
+#
+
+class UnknownIndexPlugin(Exception):
+    pass
+
+
+def check_params(params, mode, module):
+    missed_params = []
+    for key in OPERATIONS[mode]['required']:
+        if params[key] is None:
+            missed_params.append(key)
+
+    if missed_params:
+        module.fail_json(msg="missing required arguments: %s" % (",".join(missed_params)))
+
+
+def _recreate_user(module, db, user, password, roles):
+    try:
+        db.command("dropUser", user)
+        db.command("createUser", user, pwd=password, roles=roles)
+    except Exception as e:
+        module.fail_json(msg='Unable to create user: %s' % to_native(e), exception=traceback.format_exc())
+
+
+
+def user(module, client, db_name, **kwargs):
+    roles = kwargs['roles']
+    if roles is None:
+        roles = []
+    db = client[db_name]
+
+    try:
+        db.command("createUser", kwargs['user'], pwd=kwargs['password'], roles=roles)
+    except DuplicateKeyError as e:
+        if kwargs['force_update']:
+            _recreate_user(module, db, kwargs['user'], kwargs['password'], roles)
+        else:
+            module.fail_json(msg='Unable to create user: %s' % to_native(e), exception=traceback.format_exc())
+    except Exception as e:
+        module.fail_json(msg='Unable to create user: %s' % to_native(e), exception=traceback.format_exc())
+
+    module.exit_json(changed=True, user=kwargs['user'])
+
+
+def doc(module, client, db_name, **kwargs):
+    coll = client[db_name][kwargs['collection']]
+    try:
+        coll.insert_one(kwargs['doc'])
+    except DuplicateKeyError as e:
+        if kwargs['force_update']:
+            try:
+                coll.replace_one({'_id': kwargs['doc']['_id']}, kwargs['doc'])
+            except Exception as e:
+                module.fail_json(msg='Unable to insert doc: %s' % to_native(e), exception=traceback.format_exc())
+        else:
+            module.fail_json(msg='Unable to insert doc: %s' % to_native(e), exception=traceback.format_exc())
+    except Exception as e:
+        module.fail_json(msg='Unable to insert doc: %s' % to_native(e), exception=traceback.format_exc())
+
+    kwargs['doc']['_id'] = str(kwargs['doc']['_id'])
+    module.exit_json(changed=True, doc=kwargs['doc'])
+
+
+def _clean_index_direction(direction):
+    if direction in ["1", "-1"]:
+        direction = int(direction)
+
+    if direction not in [ASCENDING, DESCENDING, GEO2D, GEOHAYSTACK, GEOSPHERE, HASHED, TEXT]:
+        raise UnknownIndexPlugin("Unable to create indexes: Unknown index plugin: %s" % direction)
+    return direction
+
+
+def _clean_index_options(options):
+    res = {}
+    supported_options = set(['name', 'unique', 'background', 'sparse', 'bucketSize', 'min', 'max', 'expireAfterSeconds'])
+    for key in set(options.keys()).intersection(supported_options):
+        res[key] = options[key]
+        if key in ['min', 'max', 'bucketSize', 'expireAfterSeconds']:
+            res[key] = int(res[key])
+
+    return res
+
+
+def parse_indexes(idx):
+    keys = [(k['field'], _clean_index_direction(k['direction'])) for k in idx.pop('index')]
+    options = _clean_index_options(idx)
+    return IndexModel(keys, **options)
+
+
+def index(module, client, db_name, **kwargs):
+    parsed_indexes = map(parse_indexes, kwargs['indexes'])
+    try:
+        coll = client[db_name][kwargs['collection']]
+        coll.create_indexes(parsed_indexes)
+    except Exception as e:
+        module.fail_json(msg='Unable to create indexes: %s' % to_native(e), exception=traceback.format_exc())
+
+    module.exit_json(changed=True, indexes=kwargs['indexes'])
+
+
+OPERATIONS = {
+    'user': { 'function': user, 'params': ['user', 'password', 'roles', 'force_update'], 'required': ['user', 'password']},
+    'doc': {'function': doc, 'params': ['doc', 'collection', 'force_update'], 'required': ['doc', 'collection']},
+    'index': {'function': index, 'params': ['indexes', 'collection'], 'required': ['indexes', 'collection']}
+}
+
+
+# =========================================
+# Module execution.
+#
+
+def main():
+    module = AnsibleModule(
+        argument_spec=dict(
+            connect_string=dict(required=True),
+            database=dict(required=True, aliases=['db']),
+            mode=dict(default='user', choices=['user', 'doc', 'index']),
+            user=dict(default=None),
+            password=dict(default=None, no_log=True),
+            roles=dict(default=None, type='list'),
+            collection=dict(default=None),
+            doc=dict(default=None, type='dict'),
+            force_update=dict(default=False, type='bool'),
+            indexes=dict(default=None, type='list'),
+        )
+    )
+
+    mode = module.params['mode']
+
+    db_name = module.params['database']
+
+    params = {key: module.params[key] for key in OPERATIONS[mode]['params']}
+    check_params(params, mode, module)
+
+    try:
+        client = MongoClient(module.params['connect_string'])
+    except NameError:
+        module.fail_json(msg='the python pymongo module is required')
+    except Exception as e:
+        module.fail_json(msg='unable to connect to database: %s' % to_native(e), exception=traceback.format_exc())
+
+    OPERATIONS[mode]['function'](module, client, db_name, **params)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/openwhisk/ansible/logs.yml b/openwhisk/ansible/logs.yml
new file mode 100644
index 0000000..9109d07
--- /dev/null
+++ b/openwhisk/ansible/logs.yml
@@ -0,0 +1,81 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook is used for utilities around logs
+
+- import_playbook: properties.yml
+
+- hosts: ansible
+  tasks:
+    - name: remove "logs" folder
+      file: path="{{ openwhisk_home }}/logs" state=absent
+    - name: create "logs" folder
+      file: path="{{ openwhisk_home }}/logs" state=directory
+    - name: dump entity views
+      local_action: shell "{{ openwhisk_home }}/bin/wskadmin" db get whisks --docs --view whisks.v2.1.0/{{ item }} | tail -n +2 > "{{ openwhisk_home }}/logs/db-{{ item }}.log"
+      with_items:
+        - actions
+        - triggers
+        - rules
+        - packages
+      when: "'db' not in exclude_logs_from"
+    - name: dump activations and subjects database
+      local_action: shell "{{ openwhisk_home }}/bin/wskadmin" db get {{ item }} --docs | tail -n +2 > "{{ openwhisk_home }}/logs/db-{{ item }}.log"
+      with_items:
+        - activations
+        - subjects
+      when: "'db' not in exclude_logs_from"
+    - name: create "test reports" folder
+      file: path="{{ openwhisk_home }}/logs/test-reports" state=directory
+    - name: collect test reports
+      local_action: shell cp -r "{{ openwhisk_home }}/tests/build/{{ item }}" "{{ openwhisk_home }}/logs/test-reports/"
+      with_items:
+        - reports
+        - test-results
+      ignore_errors: true
+      when: "'tests' not in exclude_logs_from"
+    - name: create "perf reports" folder
+      file: path="{{ openwhisk_home }}/logs/perf-reports" state=directory
+    - name: collect perf logs
+      local_action: shell cp  "{{ openwhisk_home }}/tests/performance/gatling_tests/build/gatling.log" "{{ openwhisk_home }}/logs/perf-reports/"
+      ignore_errors: true
+
+- hosts: all:!ansible
+  serial: 1
+  tasks:
+  - name: init var docker_host_flag
+    set_fact:
+      docker_host_flag: ""
+  - name: set host flag when using docker remote API
+    set_fact:
+      docker_host_flag: "--host tcp://{{ ansible_host }}:{{ docker.port }}"
+    when: environmentInformation.type != "local"
+  - name: get all docker containers
+    local_action: shell docker {{ docker_host_flag }} ps -a --format="{% raw %}{{.Names}}{% endraw %}"
+    register: container_names
+  - name: get logs from all containers
+    local_action: shell docker {{ docker_host_flag }} logs {{ item }} > "{{ openwhisk_home }}/logs/{{ item }}.log" 2>&1; exit 0
+    with_items: "{{ container_names.stdout_lines | difference('whisk_docker_registry') }}"
+    when: "'docker' not in exclude_logs_from"
+  - name: workaround to make synchronize work
+    set_fact:
+      ansible_ssh_private_key_file: "{{ ansible_ssh_private_key_file }}"
+    when: ansible_ssh_private_key_file is defined
+  - name: fetch logs from all machines
+    synchronize: src="{{ whisk_logs_dir }}/" dest="{{ openwhisk_home }}/logs" mode=pull
+    when: "'machine' not in exclude_logs_from"
+    ignore_errors: true
diff --git a/openwhisk/ansible/mongodb.yml b/openwhisk/ansible/mongodb.yml
new file mode 100644
index 0000000..2a0b4f6
--- /dev/null
+++ b/openwhisk/ansible/mongodb.yml
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys a MongoDB for Openwhisk.
+
+- hosts: localhost
+  tasks:
+  - name: check if db_local.ini exists?
+    tags: ini
+    stat: path="{{ playbook_dir }}/db_local.ini"
+    register: db_check
+
+  - name: prepare db_local.ini
+    tags: ini
+    local_action: template src="db_local.ini.j2" dest="{{ playbook_dir }}/db_local.ini"
+    when: not db_check.stat.exists
+
+# This is for test, only deploy it on the first node, please use a shard cluster mongodb for
+# production env
+- hosts: db[0]
+  roles:
+    - mongodb
diff --git a/openwhisk/ansible/openwhisk.yml b/openwhisk/ansible/openwhisk.yml
new file mode 100644
index 0000000..f832b62
--- /dev/null
+++ b/openwhisk/ansible/openwhisk.yml
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook deploys an Openwhisk stack.
+# It assumes you have already set up your database with the respective db provider
+# playbook (currently cloudant.yml or couchdb.yml).
+# It assumes that wipe.yml have being deployed at least once.
+
+- import_playbook: etcd.yml
+  when: enable_scheduler
+
+- import_playbook: kafka.yml
+  when: not lean
+
+- import_playbook: controller.yml
+
+- import_playbook: scheduler.yml
+  when: enable_scheduler
+
+- import_playbook: invoker.yml
+  when: not lean
+
+- import_playbook: edge.yml
+
+- import_playbook: downloadcli.yml
diff --git a/openwhisk/ansible/postdeploy.yml b/openwhisk/ansible/postdeploy.yml
new file mode 100644
index 0000000..e3aa507
--- /dev/null
+++ b/openwhisk/ansible/postdeploy.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook installs additional packages after whisk has been deployed.
+
+- import_playbook: properties.yml
+
+- hosts: ansible
+  tasks:
+    - include_tasks: tasks/installOpenwhiskCatalog.yml
+      when: (mode == "deploy")
+      with_dict: "{{ catalog_repos }}"
diff --git a/openwhisk/ansible/prereq.yml b/openwhisk/ansible/prereq.yml
new file mode 100644
index 0000000..cc5a91d
--- /dev/null
+++ b/openwhisk/ansible/prereq.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook prepares all target hosts for Openwhisk installation.
+# It will install all necessary packages to run Openwhisk playbooks.
+
+- hosts: all:!ansible
+  serial: 1
+  roles:
+  - prereq
diff --git a/openwhisk/ansible/properties.yml b/openwhisk/ansible/properties.yml
new file mode 100644
index 0000000..6885050
--- /dev/null
+++ b/openwhisk/ansible/properties.yml
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook writes whisk.properties.
+
+- hosts: ansible
+  tasks:
+    - import_tasks: tasks/writeWhiskProperties.yml
+      when: mode == "deploy"
diff --git a/openwhisk/ansible/publish.yml b/openwhisk/ansible/publish.yml
new file mode 100644
index 0000000..505da97
--- /dev/null
+++ b/openwhisk/ansible/publish.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook updates CLIs and SDKs on an existing edge host.
+# Artifacts get built and published to NGINX. This assumes an already running egde host in an Openwhisk deployment.
+
+- hosts: edge
+  roles:
+  - cli
+  - sdk
diff --git a/openwhisk/ansible/recreateDesignDocs.yml b/openwhisk/ansible/recreateDesignDocs.yml
new file mode 100644
index 0000000..4390d11
--- /dev/null
+++ b/openwhisk/ansible/recreateDesignDocs.yml
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This playbook recreates all design documents in the whisks and the activations DB.
+
+- hosts: ansible
+  tasks:
+    - import_tasks: tasks/recreateViews.yml
+      when: mode == "deploy"
diff --git a/openwhisk/ansible/roles/apigateway/tasks/clean.yml b/openwhisk/ansible/roles/apigateway/tasks/clean.yml
new file mode 100644
index 0000000..48e8f50
--- /dev/null
+++ b/openwhisk/ansible/roles/apigateway/tasks/clean.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove apigateway container.
+
+- name: remove apigateway
+  docker_container:
+    name: apigateway
+    state: absent
+  ignore_errors: True
diff --git a/openwhisk/ansible/roles/apigateway/tasks/deploy.yml b/openwhisk/ansible/roles/apigateway/tasks/deploy.yml
new file mode 100644
index 0000000..dd9d3d6
--- /dev/null
+++ b/openwhisk/ansible/roles/apigateway/tasks/deploy.yml
@@ -0,0 +1,46 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install apigateway
+
+- name: (re)start apigateway
+  docker_container:
+    name: apigateway
+    image: "{{ apigateway.docker_image | default('openwhisk/apigateway:' ~ apigateway.version) }}"
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    hostname: apigateway
+    env:
+      "REDIS_HOST": "{{ groups['redis'] | first }}"
+      "REDIS_PORT": "{{ redis.port }}"
+      "REDIS_PASS": "{{ redis.password }}"
+      "PUBLIC_MANAGEDURL_HOST": "{{ ansible_host }}"
+      "PUBLIC_MANAGEDURL_PORT": "{{ apigateway.port.mgmt }}"
+      "TZ": "{{ docker.timezone }}"
+    ports:
+      - "{{ apigateway.port.mgmt }}:8080"
+      - "{{ apigateway.port.api }}:9000"
+    pull: "{{ apigateway_local_build is undefined }}"
+
+- name: wait until the API Gateway in this host is up and running
+  uri:
+    url: "http://{{ groups['apigateway'] | first }}:{{ apigateway.port.api }}/v1/apis"
+  register: result
+  until: result.status == 200
+  retries: 12
+  delay: 5
diff --git a/openwhisk/ansible/roles/apigateway/tasks/main.yml b/openwhisk/ansible/roles/apigateway/tasks/main.yml
new file mode 100644
index 0000000..1d2c0c1
--- /dev/null
+++ b/openwhisk/ansible/roles/apigateway/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install apigateway in the group 'apigateway' in the environment inventory
+# In deploy mode it will deploy apigateway.
+# In clean mode it will remove the apigateway containers.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/cli-install/tasks/clean.yml b/openwhisk/ansible/roles/cli-install/tasks/clean.yml
new file mode 100644
index 0000000..2617b50
--- /dev/null
+++ b/openwhisk/ansible/roles/cli-install/tasks/clean.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+
+- name: remove wsk binary
+  file:
+    path: "{{ openwhisk_home }}/bin/{{ wsk }}"
+    state: absent
+  vars:
+    wsk: "{{ ( ansible_system == 'Windows') | ternary('wsk.exe', 'wsk') }}"
diff --git a/openwhisk/ansible/roles/cli-install/tasks/deploy.yml b/openwhisk/ansible/roles/cli-install/tasks/deploy.yml
new file mode 100644
index 0000000..8cf30a7
--- /dev/null
+++ b/openwhisk/ansible/roles/cli-install/tasks/deploy.yml
@@ -0,0 +1,30 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Install the appropriate CLI into the ansible host
+
+- name: grab the local CLI from the binaries unarchived into nginx
+  get_url:
+    url: "https://{{host}}/cli/go/download/{{os}}/{{arch}}/{{wsk}}"
+    dest: "{{ openwhisk_home }}/bin"
+    mode: "0755"
+    validate_certs: no
+  vars:
+    host: "{{ groups['edge'] | first }}"
+    arch: "{{ ansible_machine | replace ('x86_64', 'amd64') }}"
+    os: "{{ ansible_system | lower | replace('darwin', 'mac') }}"
+    wsk: "{{ ( ansible_system == 'Windows') | ternary('wsk.exe', 'wsk') }}"
diff --git a/openwhisk/ansible/roles/cli-install/tasks/main.yml b/openwhisk/ansible/roles/cli-install/tasks/main.yml
new file mode 100644
index 0000000..8973ada
--- /dev/null
+++ b/openwhisk/ansible/roles/cli-install/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will configure and publish the CLI tarball.
+# In deploy mode it will generate a new CLI config, generate a tarball and copy it to nginx.
+# In clean mode it will clean the cli.nginxdir
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/cli/tasks/clean.yml b/openwhisk/ansible/roles/cli/tasks/clean.yml
new file mode 100644
index 0000000..3aaecb7
--- /dev/null
+++ b/openwhisk/ansible/roles/cli/tasks/clean.yml
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+
+- name: remove cli nginx directory
+  file:
+    path: "{{ openwhisk_cli.nginxdir.name }}"
+    state: absent
+  become: "{{ openwhisk_cli.nginxdir.become }}"
diff --git a/openwhisk/ansible/roles/cli/tasks/deploy.yml b/openwhisk/ansible/roles/cli/tasks/deploy.yml
new file mode 100644
index 0000000..4e43947
--- /dev/null
+++ b/openwhisk/ansible/roles/cli/tasks/deploy.yml
@@ -0,0 +1,87 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Tasks for handling CLI customization and publishing
+#
+# Note:  The configuration directory is actually located on the local machine;
+#        this script is run under the local host, usually 172.17.0.1 (docker local)
+
+- name: "Ensure nginx directory for cli exists"
+  file:
+    path: "{{ openwhisk_cli.nginxdir.name }}"
+    state: directory
+  become: "{{ openwhisk_cli.nginxdir.become }}"
+
+- name: "Ensure temporary directory exists"
+  file:
+    path: "{{ nginx.confdir }}/cli_temp"
+    state: directory
+
+- name: "Download release archive to build directory ..."
+  get_url:
+    url: "{{ openwhisk_cli.remote.location }}/{{ openwhisk_cli.archive_name}}-{{ openwhisk_cli_tag }}-all.tgz"
+    dest: "{{ nginx.confdir }}/cli_temp/{{ openwhisk_cli.archive_name }}.tgz"
+    headers: "{{ openwhisk_cli.remote.headers | default('{}') }}"
+  when: openwhisk_cli.installation_mode == "remote"
+
+- name: "... or Copy release archive to build directory"
+  copy:
+    src: "{{ openwhisk_cli_home }}/release/{{ openwhisk_cli.archive_name}}-{{ openwhisk_cli_tag }}-all.tgz"
+    dest: "{{ nginx.confdir }}/cli_temp/{{ openwhisk_cli.archive_name }}.tgz"
+  when: openwhisk_cli.installation_mode == "local"
+
+# Unarchiving here may fail for a local Mac development environment with an error stating "GNU tar required." To fix the
+# problem run "brew install gnu-tar".
+- name: "Expand the archive into the build directory"
+  unarchive:
+    src: "{{ nginx.confdir }}/cli_temp/{{ openwhisk_cli.archive_name }}.tgz"
+    dest: "{{ openwhisk_cli.nginxdir.name }}"
+    remote_src: yes
+
+- name: "Delete temp directory"
+  file:
+    path: "{{ nginx.confdir }}/cli_temp"
+    state: absent
+    force: yes
+
+- name: "Generate a list of individual tarballs to expand"
+  find:
+      paths: "{{ openwhisk_cli.nginxdir.name }}"
+      patterns: '*.tgz'
+      recurse: true
+  register: individual_tarballs
+
+- name: "Unarchive the individual tarballs"
+  unarchive:
+    src: "{{ item.path }}"
+    dest: "{{ item.path | dirname }}"
+    remote_src: yes
+  with_items: "{{ individual_tarballs.files }}"
+
+- name: "Generate a list of individual zipfiles to expand"
+  find:
+      paths: "{{ openwhisk_cli.nginxdir.name }}"
+      patterns: '*.zip'
+      recurse: true
+  register: individual_zipfiles
+
+- name: "Unarchive the individual zipfiles into binaries"
+  unarchive:
+    src: "{{ item.path }}"
+    dest: "{{ item.path | dirname }}"
+    remote_src: yes
+  with_items: "{{ individual_zipfiles.files }}"
diff --git a/openwhisk/ansible/roles/cli/tasks/main.yml b/openwhisk/ansible/roles/cli/tasks/main.yml
new file mode 100644
index 0000000..8973ada
--- /dev/null
+++ b/openwhisk/ansible/roles/cli/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will configure and publish the CLI tarball.
+# In deploy mode it will generate a new CLI config, generate a tarball and copy it to nginx.
+# In clean mode it will clean the cli.nginxdir
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/controller/tasks/clean.yml b/openwhisk/ansible/roles/controller/tasks/clean.yml
new file mode 100644
index 0000000..bf99436
--- /dev/null
+++ b/openwhisk/ansible/roles/controller/tasks/clean.yml
@@ -0,0 +1,40 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove controller containers.
+
+- name: get controller name
+  set_fact:
+    controller_name: "{{ name_prefix ~ host_group.index(inventory_hostname) }}"
+
+- name: remove controller
+  docker_container:
+    name: "{{ controller_name }}"
+    state: absent
+  ignore_errors: "True"
+
+- name: remove controller log directory
+  file:
+    path: "{{ whisk_logs_dir }}/{{ controller_name }}"
+    state: absent
+  become: "{{ logs.dir.become }}"
+
+- name: remove controller conf directory
+  file:
+    path: "{{ controller.confdir }}/{{ controller_name }}"
+    state: absent
+  become: "{{ controller.dir.become }}"
diff --git a/openwhisk/ansible/roles/controller/tasks/deploy.yml b/openwhisk/ansible/roles/controller/tasks/deploy.yml
new file mode 100644
index 0000000..5c46392
--- /dev/null
+++ b/openwhisk/ansible/roles/controller/tasks/deploy.yml
@@ -0,0 +1,482 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install Controller in group 'controllers' in the environment
+# inventory
+
+- import_tasks: docker_login.yml
+
+- name: get controller name and index
+  set_fact:
+    controller_name: "{{ name_prefix ~ host_group.index(inventory_hostname) }}"
+    controller_index:
+      "{{ (controller_index_base|int) + host_group.index(inventory_hostname) }}"
+
+- name: "pull the {{ docker.image.tag }} image of controller"
+  shell: "docker pull {{docker_registry}}{{ docker.image.prefix }}/controller:{{docker.image.tag}}"
+  when: docker_registry != ""
+  register: result
+  until: (result.rc == 0)
+  retries: "{{ docker.pull.retries }}"
+  delay: "{{ docker.pull.delay }}"
+
+- name: ensure controller log directory is created with permissions
+  file:
+    path: "{{ whisk_logs_dir }}/{{ controller_name }}"
+    state: directory
+    mode: 0777
+  become: "{{ logs.dir.become }}"
+
+# We need to create the file with proper permissions because the dir creation above
+# does not result in a dir with full permissions in docker machine especially with macos mounts
+- name: ensure controller log file is created with permissions
+  file:
+    path: "{{ whisk_logs_dir }}/{{ controller_name }}/{{ controller_name }}_logs.log"
+    state: touch
+    mode: 0777
+  when: environment_type is defined and environment_type == "docker-machine"
+
+- name: ensure controller config directory is created with permissions
+  file:
+    path: "{{ controller.confdir }}/{{ controller_name }}"
+    state: directory
+    mode: 0777
+  become: "{{ controller.dir.become }}"
+
+- name: copy jmxremote password file
+  when: jmx.enabled
+  template:
+    src: "jmxremote.password.j2"
+    dest: "{{ controller.confdir }}/{{ controller_name }}/jmxremote.password"
+    mode: 0777
+
+- name: copy jmxremote access file
+  when: jmx.enabled
+  template:
+    src: "jmxremote.access.j2"
+    dest: "{{ controller.confdir }}/{{ controller_name }}/jmxremote.access"
+    mode: 0777
+
+- name: "copy kafka truststore/keystore"
+  when: kafka.protocol == 'SSL'
+  copy:
+    src:
+      "{{openwhisk_home~'/ansible/roles/kafka/files/'~kafka.ssl.keystore.name}}"
+    dest: "{{ controller.confdir }}/{{ controller_name }}"
+
+- name: copy nginx certificate keystore
+  when: controller.protocol == 'https'
+  copy:
+    src: files/{{ controller.ssl.keystore.name }}
+    mode: 0666
+    dest: "{{ controller.confdir }}/{{ controller_name }}"
+  become: "{{ controller.dir.become }}"
+
+- name: copy certificates
+  when: controller.protocol == 'https'
+  copy:
+    src: "{{ openwhisk_home }}/ansible/roles/controller/files/{{ item }}"
+    mode: 0666
+    dest: "{{ controller.confdir }}/{{ controller_name }}"
+  with_items:
+    - "{{ controller.ssl.cert }}"
+    - "{{ controller.ssl.key }}"
+  become: "{{ controller.dir.become }}"
+
+- name: check, that required databases exist
+  include_tasks: "{{ openwhisk_home }}/ansible/tasks/db/checkDb.yml"
+  vars:
+    dbName: "{{ item }}"
+    dbUser: "{{ db.credentials.controller.user }}"
+    dbPass: "{{ db.credentials.controller.pass }}"
+  with_items:
+    - "{{ db.whisk.actions }}"
+    - "{{ db.whisk.auth }}"
+    - "{{ db.whisk.activations }}"
+
+- name: prepare controller port
+  set_fact:
+    controller_port: "{{ controller.basePort + (controller_index | int) }}"
+    ports_to_expose:
+      - "{{ controller.basePort + (controller_index | int) }}:8080"
+
+- name: expose additional ports if jmxremote is enabled
+  when: jmx.enabled
+  vars:
+    jmx_remote_port: "{{ jmx.basePortController + (controller_index|int) }}"
+    jmx_remote_rmi_port:
+      "{{ jmx.rmiBasePortController + (controller_index|int) }}"
+  set_fact:
+    ports_to_expose: >-
+      {{ ports_to_expose }} +
+      [ '{{ jmx_remote_port }}:{{ jmx_remote_port }}' ] +
+      [ '{{ jmx_remote_rmi_port }}:{{ jmx_remote_rmi_port }}' ]
+    controller_args: >-
+      {{ controller.arguments }}
+      {{ jmx.jvmCommonArgs }}
+      -Djava.rmi.server.hostname={{ inventory_hostname }}
+      -Dcom.sun.management.jmxremote.rmi.port={{ jmx_remote_rmi_port }}
+      -Dcom.sun.management.jmxremote.port={{ jmx_remote_port }}
+
+- name: Load config from template
+  set_fact:
+      openwhisk_config: "{{ lookup('template', 'config.j2') | b64encode }}"
+
+- name: populate environment variables for controller
+  set_fact:
+    env:
+      "JAVA_OPTS":
+        -Xmx{{ controller.heap }}
+        -XX:+CrashOnOutOfMemoryError
+        -XX:+UseGCOverheadLimit
+        -XX:ErrorFile=/logs/java_error.log
+        -XX:+HeapDumpOnOutOfMemoryError
+        -XX:HeapDumpPath=/logs
+      "CONTROLLER_OPTS": "{{ controller_args | default(controller.arguments) }}"
+      "JMX_REMOTE": "{{ jmx.enabled }}"
+      "OPENWHISK_ENCODED_CONFIG": "{{ openwhisk_config }}"
+      "PORT": "8080"
+      "TZ": "{{ docker.timezone }}"
+
+      "CONFIG_whisk_info_date": "{{ whisk.version.date }}"
+      "CONFIG_whisk_info_buildNo": "{{ docker.image.tag }}"
+      "CONFIG_whisk_cluster_name": "{{ whisk.cluster_name | lower }}"
+      "CONFIG_whisk_controller_username": "{{ controller.username }}"
+      "CONFIG_whisk_controller_password": "{{ controller.password }}"
+
+      "KAFKA_HOSTS": "{{ kafka_connect_string }}"
+      "CONFIG_whisk_kafka_replicationFactor":
+        "{{ kafka.replicationFactor | default() }}"
+      "CONFIG_whisk_kafka_topics_cacheInvalidation_retentionBytes":
+        "{{ kafka_topics_cacheInvalidation_retentionBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_cacheInvalidation_retentionMs":
+        "{{ kafka_topics_cacheInvalidation_retentionMS | default() }}"
+      "CONFIG_whisk_kafka_topics_cacheInvalidation_segmentBytes":
+        "{{ kafka_topics_cacheInvalidation_segmentBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_completed_retentionBytes":
+        "{{ kafka_topics_completed_retentionBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_completed_retentionMs":
+        "{{ kafka_topics_completed_retentionMS | default() }}"
+      "CONFIG_whisk_kafka_topics_completed_segmentBytes":
+        "{{ kafka_topics_completed_segmentBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_health_retentionBytes":
+        "{{ kafka_topics_health_retentionBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_health_retentionMs":
+        "{{ kafka_topics_health_retentionMS | default() }}"
+      "CONFIG_whisk_kafka_topics_health_segmentBytes":
+        "{{ kafka_topics_health_segmentBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_prefix":
+        "{{ kafka.topicsPrefix }}"
+      "CONFIG_whisk_kafka_topics_userEvent_prefix":
+        "{{ kafka.topicsUserEventPrefix }}"
+      "CONFIG_whisk_kafka_common_securityProtocol":
+        "{{ kafka.protocol }}"
+      "CONFIG_whisk_kafka_common_sslTruststoreLocation":
+        "/conf/{{ kafka.ssl.keystore.name }}"
+      "CONFIG_whisk_kafka_common_sslTruststorePassword":
+        "{{ kafka.ssl.keystore.password }}"
+      "CONFIG_whisk_kafka_common_sslKeystoreLocation":
+        "/conf/{{ kafka.ssl.keystore.name }}"
+      "CONFIG_whisk_kafka_common_sslKeystorePassword":
+        "{{ kafka.ssl.keystore.password }}"
+
+      "CONFIG_whisk_couchdb_protocol": "{{ db.protocol }}"
+      "CONFIG_whisk_couchdb_host": "{{ db.host }}"
+      "CONFIG_whisk_couchdb_port": "{{ db.port }}"
+      "CONFIG_whisk_couchdb_username": "{{ db.credentials.controller.user }}"
+      "CONFIG_whisk_couchdb_password": "{{ db.credentials.controller.pass }}"
+      "CONFIG_whisk_couchdb_provider": "{{ db.provider }}"
+      "CONFIG_whisk_couchdb_databases_WhiskAuth": "{{ db.whisk.auth }}"
+      "CONFIG_whisk_couchdb_databases_WhiskEntity": "{{ db.whisk.actions }}"
+      "CONFIG_whisk_couchdb_databases_WhiskActivation":
+        "{{ db.whisk.activations }}"
+      "CONFIG_whisk_db_subjectsDdoc": "{{ db_whisk_subjects_ddoc | default() }}"
+      "CONFIG_whisk_db_actionsDdoc": "{{ db_whisk_actions_ddoc | default() }}"
+      "CONFIG_whisk_db_activationsDdoc": "{{ db_whisk_activations_ddoc | default() }}"
+      "CONFIG_whisk_db_activationsFilterDdoc": "{{ db_whisk_activations_filter_ddoc | default() }}"
+      "CONFIG_whisk_userEvents_enabled": "{{ user_events | default(false) | lower }}"
+
+      "LIMITS_ACTIONS_INVOKES_PERMINUTE": "{{ limits.invocationsPerMinute }}"
+      "LIMITS_ACTIONS_INVOKES_CONCURRENT": "{{ limits.concurrentInvocations }}"
+      "LIMITS_TRIGGERS_FIRES_PERMINUTE": "{{ limits.firesPerMinute }}"
+      "LIMITS_ACTIONS_SEQUENCE_MAXLENGTH": "{{ limits.sequenceMaxLength }}"
+
+      "CONFIG_whisk_memory_min": "{{ limit_action_memory_min | default() }}"
+      "CONFIG_whisk_memory_max": "{{ limit_action_memory_max | default() }}"
+      "CONFIG_whisk_memory_std": "{{ limit_action_memory_std | default() }}"
+
+      "CONFIG_whisk_timeLimit_min": "{{ limit_action_time_min | default() }}"
+      "CONFIG_whisk_timeLimit_max": "{{ limit_action_time_max | default() }}"
+      "CONFIG_whisk_timeLimit_std": "{{ limit_action_time_std | default() }}"
+
+      "CONFIG_whisk_concurrencyLimit_min": "{{ limit_action_concurrency_min | default() }}"
+      "CONFIG_whisk_concurrencyLimit_max": "{{ limit_action_concurrency_max | default() }}"
+      "CONFIG_whisk_concurrencyLimit_std": "{{ limit_action_concurrency_std | default() }}"
+
+      "CONFIG_whisk_featureFlags_requireApiKeyAnnotation": "{{ whisk.feature_flags.require_api_key_annotation | default(true) | lower }}"
+      "CONFIG_whisk_featureFlags_requireResponsePayload": "{{ whisk.feature_flags.require_response_payload | default(true) | lower }}"
+
+      "CONFIG_whisk_activation_payload_max":
+        "{{ limit_activation_payload | default() }}"
+
+      "RUNTIMES_MANIFEST": "{{ runtimesManifest | to_json }}"
+      "CONFIG_whisk_runtimes_defaultImagePrefix":
+        "{{ runtimes_default_image_prefix | default() }}"
+      "CONFIG_whisk_runtimes_defaultImageTag":
+        "{{ runtimes_default_image_tag | default() }}"
+      "CONFIG_whisk_runtimes_bypassPullForLocalImages":
+        "{{ runtimes_bypass_pull_for_local_images | default() | lower }}"
+      "CONFIG_whisk_runtimes_localImagePrefix":
+        "{{ runtimes_local_image_prefix | default() }}"
+
+      "METRICS_KAMON": "{{ metrics.kamon.enabled | default(false) | lower }}"
+      "METRICS_KAMON_TAGS": "{{ metrics.kamon.tags | default() | lower }}"
+      "METRICS_LOG": "{{ metrics.log.enabled | default(false) | lower }}"
+      "CONFIG_whisk_controller_protocol": "{{ controller.protocol }}"
+      "CONFIG_whisk_controller_https_keystorePath":
+        "/conf/{{ controller.ssl.keystore.name }}"
+      "CONFIG_whisk_controller_https_keystorePassword":
+        "{{ controller.ssl.keystore.password }}"
+      "CONFIG_whisk_controller_https_keystoreFlavor":
+        "{{ controller.ssl.storeFlavor }}"
+      "CONFIG_whisk_controller_https_clientAuth":
+        "{{ controller.ssl.clientAuth }}"
+      "CONFIG_whisk_loadbalancer_managedFraction":
+        "{{ controller.managedFraction }}"
+      "CONFIG_whisk_loadbalancer_blackboxFraction":
+        "{{ controller.blackboxFraction }}"
+      "CONFIG_whisk_loadbalancer_timeoutFactor":
+        "{{ controller.timeoutFactor }}"
+      "CONFIG_whisk_loadbalancer_timeoutAddon":
+        "{{ controller.timeoutAddon }}"
+
+      "CONFIG_kamon_statsd_hostname": "{{ metrics.kamon.host }}"
+      "CONFIG_kamon_statsd_port": "{{ metrics.kamon.port }}"
+
+      "CONFIG_whisk_spi_LogStoreProvider": "{{ userLogs.spi }}"
+      "CONFIG_whisk_spi_LoadBalancerProvider":
+        "{{ controller.loadbalancer.spi }}"
+      "CONFIG_whisk_spi_EntitlementSpiProvider": "{{ controller.entitlement.spi }}"
+
+      "CONFIG_whisk_spi_AuthenticationDirectiveProvider": "{{ controller.authentication.spi }}"
+      "CONFIG_logback_log_level": "{{ controller.loglevel }}"
+
+      "CONFIG_whisk_transactions_header": "{{ transactions.header }}"
+
+      "CONFIG_whisk_controller_activation_pollingFromDb": "{{ controller_activation_pollingFromDb | default(true) | lower }}"
+
+- name: merge extra env variables
+  set_fact:
+    env: "{{ env | combine(controller.extraEnv) }}"
+
+- name: setup elasticsearch activation store env
+  set_fact:
+    elastic_env:
+      "CONFIG_whisk_activationStore_elasticsearch_protocol": "{{ db.elasticsearch.protocol}}"
+      "CONFIG_whisk_activationStore_elasticsearch_hosts": "{{ elasticsearch_connect_string }}"
+      "CONFIG_whisk_activationStore_elasticsearch_indexPattern": "{{ db.elasticsearch.index_pattern }}"
+      "CONFIG_whisk_activationStore_elasticsearch_username": "{{ db.elasticsearch.auth.admin.username }}"
+      "CONFIG_whisk_activationStore_elasticsearch_password": "{{ db.elasticsearch.auth.admin.password }}"
+      "CONFIG_whisk_spi_ActivationStoreProvider": "org.apache.openwhisk.core.database.elasticsearch.ElasticSearchActivationStoreProvider"
+  when: db.activation_store.backend == "ElasticSearch"
+
+- name: merge elasticsearch activation store env
+  set_fact:
+    env: "{{ env | combine(elastic_env) }}"
+  when: db.activation_store.backend == "ElasticSearch"
+
+- name: setup mongodb artifact store env
+  set_fact:
+    mongodb_env:
+      "CONFIG_whisk_mongodb_uri": "{{ db.mongodb.connect_string }}"
+      "CONFIG_whisk_mongodb_database": "{{ db.mongodb.database }}"
+      "CONFIG_whisk_spi_ArtifactStoreProvider": "org.apache.openwhisk.core.database.mongodb.MongoDBArtifactStoreProvider"
+  when: db.artifact_store.backend == "MongoDB"
+
+- name: merge mongodb artifact store env
+  set_fact:
+    env: "{{ env | combine(mongodb_env) }}"
+  when: db.artifact_store.backend == "MongoDB"
+
+- name: setup scheduler env
+  set_fact:
+    scheduler_env:
+      "CONFIG_whisk_etcd_hosts": "{{ etcd_connect_string }}"
+      "CONFIG_whisk_etcd_lease_timeout": "{{ etcd.lease.timeout }}"
+      "CONFIG_whisk_etcd_pool_threads": "{{ etcd.pool_threads }}"
+      "CONFIG_whisk_scheduler_grpc_tls": "{{ scheduler.grpc.tls | default('false') | lower }}"
+      "CONFIG_whisk_scheduler_maxPeek": "{{ scheduler.maxPeek }}"
+      "CONFIG_whisk_spi_LoadBalancerProvider": "org.apache.openwhisk.core.loadBalancer.FPCPoolBalancer"
+      "CONFIG_whisk_spi_EntitlementSpiProvider": "org.apache.openwhisk.core.entitlement.FPCEntitlementProvider"
+  when: enable_scheduler
+
+- name: merge scheduler env
+  set_fact:
+    env: "{{ env | combine(scheduler_env) }}"
+  when: enable_scheduler
+
+- name: populate volumes for controller
+  set_fact:
+    controller_volumes:
+       - "{{ whisk_logs_dir }}/{{ controller_name }}:/logs"
+       - "{{ controller.confdir }}/{{ controller_name }}:/conf"
+
+- name: check if coverage collection is enabled
+  set_fact:
+    coverage_enabled: false
+  when: coverage_enabled is undefined
+
+- name: ensure controller coverage directory is created with permissions
+  file:
+    path: "{{ coverage_logs_dir }}/controller/{{ item }}"
+    state: directory
+    mode: 0777
+  with_items:
+    - controller
+    - common
+  become: "{{ logs.dir.become }}"
+  when: coverage_enabled
+
+- name: extend controller volume for coverage
+  set_fact:
+    controller_volumes: "{{ controller_volumes|default({}) + [coverage_logs_dir+'/controller:/coverage']  }}"
+  when: coverage_enabled
+
+- name: include plugins
+  include_tasks: "{{ item }}.yml"
+  with_items: "{{ controller_plugins | default([]) }}"
+  when: not lean
+
+- name: lean controller setup
+  include_tasks: "lean.yml"
+  when: lean
+
+# Before redeploy controller, should remove that controller instance from nginx
+- name: remove the controller from nginx's upstream configuration
+  shell:
+    docker exec -t nginx sh -c "sed -i  \"s/ server {{ ansible_host }}:{{ controller.basePort + (controller_index | int) }}/ \#server {{ ansible_host }}:{{ controller.basePort + (controller_index | int) }}/g\"  /etc/nginx/nginx.conf  && nginx -s reload"
+  delegate_to: "{{ item }}"
+  with_items: "{{ groups['edge'] }}"
+  when: zeroDowntimeDeployment.enabled == true
+
+- name: wait some time for controllers fire all existing triggers
+  shell: sleep 5s
+  when: zeroDowntimeDeployment.enabled == true
+
+- name: wait until {{ controller_name }} executes all existing activations
+  uri:
+    url: "{{ controller.protocol }}://{{ ansible_host }}:{{ controller.basePort + (controller_index | int) }}/activation/count"
+    validate_certs: no
+    client_key: "{{ controller.confdir }}/controller{{ groups['controllers'].index(inventory_hostname) }}/{{ controller.ssl.key }}"
+    client_cert: "{{ controller.confdir }}/controller{{ groups['controllers'].index(inventory_hostname) }}/{{ controller.ssl.cert }}"
+    return_content: yes
+    user: "{{ controller.username }}"
+    password: "{{ controller.password }}"
+    force_basic_auth: yes
+  register: result
+  until: result.content == '0'
+  retries: "{{ controller.deployment.retries }}"
+  delay: "{{ controller.deployment.delay }}"
+  when: zeroDowntimeDeployment.enabled == true
+  ignore_errors: "{{ controller.deployment.ignore_error }}"
+
+- name: Disable {{ controller_name }} before remove controller
+  uri:
+    url: "{{ controller.protocol }}://{{ ansible_host }}:{{ controller.basePort + groups['controllers'].index(inventory_hostname) }}/disable"
+    validate_certs: no
+    client_key: "{{ controller.confdir }}/{{ controller_name }}/{{ controller.ssl.key }}"
+    client_cert: "{{ controller.confdir }}/{{ controller_name }}/{{ controller.ssl.cert }}"
+    method: POST
+    status_code: 200
+    user: "{{ controller.username }}"
+    password: "{{ controller.password }}"
+    force_basic_auth: yes
+  ignore_errors: "{{ controller.deployment.ignore_error }}"
+  when: zeroDowntimeDeployment.enabled == true
+
+- name: wait some time for controller to gracefully shutdown the consumer for activation ack
+  shell: sleep 5s
+  when: zeroDowntimeDeployment.enabled == true
+
+- name: (re)start controller
+  docker_container:
+    name: "{{ controller_name }}"
+    image:
+      "{{docker_registry~docker.image.prefix}}/controller:{{ 'cov' if (coverage_enabled) else docker.image.tag }}"
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    hostname: "{{ controller_name }}"
+    env: "{{ env }}"
+    volumes: "{{ controller_volumes }}"
+    ports: "{{ ports_to_expose }}"
+    # userns_mode, pid_mode and privileged required when controller running in lean mode
+    userns_mode: "{{ userns_mode | default('') }}"
+    pid_mode: "{{ pid_mode | default('') }}"
+    privileged: "{{ privileged | default('no') }}"
+    command:
+      /bin/sh -c
+      "exec /init.sh {{ controller_index }}
+      >> /logs/{{ controller_name }}_logs.log 2>&1"
+
+- name: wait until the Controller in this host is up and running
+  uri:
+    url:
+      "{{controller.protocol}}://{{ansible_host}}:{{controller_port}}/ping"
+    validate_certs: "no"
+    client_key:
+      "{{ controller.confdir }}/{{ controller_name }}/{{ controller.ssl.key }}"
+    client_cert:
+      "{{ controller.confdir }}/{{ controller_name }}/{{ controller.ssl.cert }}"
+  register: result
+  until: result.status == 200
+  retries: 12
+  delay: 10
+
+- name: warm up activation path
+  uri:
+    url:
+      "{{controller.protocol}}://{{ lookup('file', '{{ catalog_auth_key }}')}}@{{ansible_host}}:{{controller_port}}/api/v1/namespaces/_/actions/invokerHealthTestAction{{controller_index}}?blocking=false&result=false"
+    validate_certs: "no"
+    client_key:
+      "{{ controller.confdir }}/{{ controller_name }}/{{ controller.ssl.key }}"
+    client_cert:
+      "{{ controller.confdir }}/{{ controller_name }}/{{ controller.ssl.cert }}"
+    method: POST
+  ignore_errors: True
+
+- name: wait for all invokers in {{ controller_name }} to become up
+  uri:
+    url: "{{ controller.protocol }}://{{ ansible_host }}:{{ controller.basePort + (controller_index | int) }}/invokers"
+    validate_certs: no
+    client_key: "{{ controller.confdir }}/controller{{ groups['controllers'].index(inventory_hostname) }}/{{ controller.ssl.key }}"
+    client_cert: "{{ controller.confdir }}/controller{{ groups['controllers'].index(inventory_hostname) }}/{{ controller.ssl.cert }}"
+    return_content: yes
+  register: invokerStatus
+  until: invokerStatus.json|length >= 1 and "unhealthy" not in invokerStatus.content
+  retries: 14
+  delay: 5
+  when: zeroDowntimeDeployment.enabled == true
+
+# When all invokers report their status to controller, add the controller instance to nginx when exist at least one invoker is up
+- name: Add the controller back to nginx's upstream configuration when there exist at least one healthy invoker
+  shell:
+    docker exec -t nginx sh -c "sed -i  \"s/ \#server {{ ansible_host }}:{{ controller.basePort + (controller_index | int) }}/ server {{ ansible_host }}:{{ controller.basePort + (controller_index | int) }}/g\"  /etc/nginx/nginx.conf  && nginx -s reload"
+  delegate_to: "{{ item }}"
+  with_items: "{{ groups['edge'] }}"
+  ignore_errors: True
+  when: zeroDowntimeDeployment.enabled == true and "up" in invokerStatus.content
diff --git a/openwhisk/ansible/roles/controller/tasks/join_akka_cluster.yml b/openwhisk/ansible/roles/controller/tasks/join_akka_cluster.yml
new file mode 100644
index 0000000..dc0a1ee
--- /dev/null
+++ b/openwhisk/ansible/roles/controller/tasks/join_akka_cluster.yml
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+#
+#  Controller 'plugin' that will add the items necessary to the controller
+#  environment to cause the controller to join a specified akka cluster
+#
+
+- name: add akka port to ports_to_expose
+  set_fact:
+    ports_to_expose: >-
+      {{ ports_to_expose }} +
+      [ "{{ (controller.akka.cluster.basePort + (controller_index | int)) }}:"
+      + "{{ controller.akka.cluster.bindPort }}" ]
+
+- name: add seed nodes to controller environment
+  set_fact:
+    env: >-
+      {{ env | combine({
+        'CONFIG_akka_cluster_seedNodes_' ~ seedNode.0:
+          'akka://controller-actor-system@'~seedNode.1~':'~(controller.akka.cluster.basePort+seedNode.0)
+      }) }}
+  with_indexed_items: "{{ controller.akka.cluster.seedNodes }}"
+  loop_control:
+    loop_var: seedNode
+
+- name: Add akka environment to controller environment
+  vars:
+    akka_env:
+      "CONFIG_akka_actor_provider": "{{ controller.akka.provider }}"
+      "CONFIG_akka_remote_artery_canonical_hostname":
+        "{{ controller.akka.cluster.host[(controller_index | int)] }}"
+      "CONFIG_akka_remote_artery_canonical_port":
+        "{{ controller.akka.cluster.basePort + (controller_index | int) }}"
+      "CONFIG_akka_remote_artery_bind_port":
+        "{{ controller.akka.cluster.bindPort }}"
+  set_fact:
+    env: "{{ env | combine(akka_env) }}"
diff --git a/openwhisk/ansible/roles/controller/tasks/lean.yml b/openwhisk/ansible/roles/controller/tasks/lean.yml
new file mode 100644
index 0000000..79cec6c
--- /dev/null
+++ b/openwhisk/ansible/roles/controller/tasks/lean.yml
@@ -0,0 +1,54 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This plugin will  provide controller with Lean Controller parameters
+
+- name: set inventory_hostname to invoker and save controllers data that can be changed by invoker task
+  set_fact:
+    controller_env: "{{ env }}"
+    inventory_hostname: "invoker0"
+    invoker_index_base: 0
+    name_prefix: "invoker"
+    host_group: "{{ groups['invokers'] }}"
+
+- name: include invoker data
+  include_tasks: "../invoker/tasks/deploy.yml"
+
+- name: save invoker volumes
+  set_fact:
+    invoker_volumes: "{{ volumes.split(',') | reject('search','/logs') | reject('search','/conf') | reject('search','/coverage') | list }}"
+
+- name: populate volumes
+  set_fact:
+    controller_volumes: >-
+      {{ invoker_volumes }} +
+      {{ controller_volumes }}
+
+- name: populate environment variables for LEAN controller
+  vars:
+    lean_env:
+      "CONFIG_whisk_spi_MessagingProvider": "org.apache.openwhisk.connector.lean.LeanMessagingProvider"
+      "CONFIG_whisk_spi_LoadBalancerProvider": "org.apache.openwhisk.core.loadBalancer.LeanBalancer"
+  set_fact:
+    env: "{{ env | combine(controller_env) | combine(lean_env) }}"
+
+- name: provide extended docker container params for controller
+  set_fact:
+    userns_mode: "host"
+    pid_mode: "host"
+    privileged: "yes"
+
diff --git a/openwhisk/ansible/roles/controller/tasks/main.yml b/openwhisk/ansible/roles/controller/tasks/main.yml
new file mode 100644
index 0000000..e0b5320
--- /dev/null
+++ b/openwhisk/ansible/roles/controller/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install controller in group 'controllers' in the environment inventory
+# In deploy mode it will deploy controllers.
+# In clean mode it will remove the controller containers.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/controller/templates/config.j2 b/openwhisk/ansible/roles/controller/templates/config.j2
new file mode 100644
index 0000000..fecc43b
--- /dev/null
+++ b/openwhisk/ansible/roles/controller/templates/config.j2
@@ -0,0 +1,8 @@
+include classpath("application.conf")
+
+# Specify any custom config here. For example
+# whisk {
+#   metrics {
+#     prometheus-enabled = true
+#   }
+# }
diff --git a/openwhisk/ansible/roles/couchdb/tasks/clean.yml b/openwhisk/ansible/roles/couchdb/tasks/clean.yml
new file mode 100644
index 0000000..0988501
--- /dev/null
+++ b/openwhisk/ansible/roles/couchdb/tasks/clean.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove CouchDB server
+
+- name: remove CouchDB
+  docker_container:
+    name: couchdb
+    state: absent
+  ignore_errors: True
diff --git a/openwhisk/ansible/roles/couchdb/tasks/deploy.yml b/openwhisk/ansible/roles/couchdb/tasks/deploy.yml
new file mode 100644
index 0000000..f0250b1
--- /dev/null
+++ b/openwhisk/ansible/roles/couchdb/tasks/deploy.yml
@@ -0,0 +1,186 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will run a CouchDB server on the db group
+
+- name: set the coordinator to the first node
+  set_fact:
+    coordinator: "{{ groups['db'][0] }}"
+
+- name: "Set the volumes"
+  set_fact:
+    volumes: []
+
+- name: "Set the nodes"
+  set_fact:
+    couchdb_nodes: []
+
+- name: check if db credentials are valid for CouchDB
+  fail: msg="The db provider in your {{ hosts_dir }}/group_vars/all is {{ db.provider }}, it has to be CouchDB, pls double check"
+  when: db.provider != "CouchDB"
+
+- name: ensure directory for persisting db exists
+  stat:
+    path: "{{ db.persist_path }}"
+  register: db_persist_path_exists
+  when: db.persist_path
+
+- name: fail if path to persist db does not exist
+  fail:
+    msg: directory for persisting db does not exist '{{ db.persist_path }}'
+  when: db.persist_path and not (db_persist_path_exists.stat.exists and db_persist_path_exists.stat.isdir)
+
+- name: "mount directory for persisting dbs"
+  set_fact:
+    volumes: "{{ volumes }} + [ '{{ db.persist_path }}:/opt/couchdb/data' ]"
+  when: db.persist_path
+
+- include_tasks: gen_erl_cookie.yml
+  when: (db.instances|int >= 2)
+
+- name: "set the erlang cookie volume"
+  set_fact:
+    volumes: "{{ volumes }} + [ '{{ config_root_dir }}/erlang.cookie:/opt/couchdb/.erlang.cookie' ]"
+  when: (db.instances|int >= 2)
+
+- name: "(re)start CouchDB from '{{ couchdb_image }} ' "
+  vars:
+    couchdb_image: "{{ couchdb.docker_image | default('apache/couchdb:' ~ couchdb.version ) }}"
+  docker_container:
+    name: couchdb
+    image: "{{ couchdb_image }}"
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    volumes: "{{ volumes }}"
+    ports:
+      - "{{ db.port }}:5984"
+      - "4369:4369"
+      - "9100:9100"
+    env:
+      COUCHDB_USER: "{{ db.credentials.admin.user }}"
+      COUCHDB_PASSWORD: "{{ db.credentials.admin.pass }}"
+      NODENAME: "{{ ansible_host }}"
+      TZ: "{{ docker.timezone }}"
+    pull: "{{ couchdb.pull_couchdb | default(true) }}"
+
+- name: wait until CouchDB in this host is up and running
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_utils/"
+  register: result
+  until: result.status == 200
+  retries: 12
+  delay: 5
+
+- name: check if '_users' database exists
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_users"
+    method: HEAD
+    status_code: 200,404
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  register: create_users_db
+
+- name: create '_users' database for singleton mode if necessary
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_users"
+    method: PUT
+    status_code: 201
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  when: (create_users_db.status == 404) and (inventory_hostname == coordinator) and (couchdb.version is version_compare('2.0','>='))
+
+- name: check whether couchdb is clustered
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_cluster_setup"
+    method: GET
+    status_code: 200
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  register: cluster_state
+  run_once: true
+
+- name: check clustered nodes
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_membership"
+    method: GET
+    status_code: 200
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  register: nodes_state
+  run_once: true
+
+- name: generates couchdb node name
+  set_fact:
+    couchdb_nodes: "{{ couchdb_nodes }} + [ 'couchdb@{{ item }}' ]"
+  with_items: "{{ groups['db'] }}"
+  run_once: true
+
+- name: check if there is a new node
+  set_fact:
+    require_clustering: true
+  when: item not in nodes_state.json.cluster_nodes
+  with_items: "{{ couchdb_nodes }}"
+  run_once: true
+
+- name: set node name
+  set_fact:
+    node_name: "couchdb@{{ ansible_host }}"
+
+- name: enable the cluster setup mode
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_cluster_setup"
+    method: POST
+    body: >
+        {"action": "enable_cluster", "bind_address":"0.0.0.0", "username": "{{ db.credentials.admin.user }}", "password":"{{ db.credentials.admin.pass }}", "port": {{ db.port }}, "node_count": "{{ groups['db'] | length }}", "remote_node": "{{ ansible_host }}", "remote_current_user": "{{ db.credentials.admin.user }}", "remote_current_password": "{{ db.credentials.admin.pass }}"}
+    body_format: json
+    status_code: 201
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  when: (inventory_hostname == coordinator) and (db.instances|int >= 2) and require_clustering is defined
+
+- name: add remote nodes to the cluster
+  uri:
+    url: "{{ db.protocol }}://{{ coordinator }}:{{ db.port }}/_cluster_setup"
+    method: POST
+    body: >
+        {"action": "add_node", "host":"{{ ansible_host }}", "port": {{ db.port }}, "username": "{{ db.credentials.admin.user }}", "password":"{{ db.credentials.admin.pass }}"}
+    body_format: json
+    status_code: 201
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  when: (inventory_hostname != coordinator) and (db.instances|int >= 2)  and require_clustering is defined and node_name not in nodes_state.json.cluster_nodes
+
+- name: finish the cluster setup mode
+  uri:
+    url: "{{ db.protocol }}://{{ ansible_host }}:{{ db.port }}/_cluster_setup"
+    method: POST
+    body: >
+        {"action": "finish_cluster"}
+    body_format: json
+    status_code: 201
+    user: "{{ db.credentials.admin.user }}"
+    password: "{{ db.credentials.admin.pass }}"
+    force_basic_auth: yes
+  when: (inventory_hostname == coordinator) and (db.instances|int >= 2) and (cluster_state.json.state != "cluster_finished")
+
diff --git a/openwhisk/ansible/roles/couchdb/tasks/main.yml b/openwhisk/ansible/roles/couchdb/tasks/main.yml
new file mode 100644
index 0000000..b3e015c
--- /dev/null
+++ b/openwhisk/ansible/roles/couchdb/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will deploy a database server. Use the role if you want to use CouchCB locally.
+# In deploy mode it will start the CouchDB container.
+# In clean mode it will remove the CouchDB container.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/elasticsearch/tasks/clean.yml b/openwhisk/ansible/roles/elasticsearch/tasks/clean.yml
new file mode 100644
index 0000000..21109b7
--- /dev/null
+++ b/openwhisk/ansible/roles/elasticsearch/tasks/clean.yml
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove ElasticSearch server
+
+- name: set elasticsearch container name and volume
+  set_fact:
+    elasticsearch_name: "{{ name_prefix ~ host_group.index(inventory_hostname) }}"
+    volume_name: "{{ db.elasticsearch.base_volume ~ host_group.index(inventory_hostname) }}"
+
+- name: remove ElasticSearch
+  vars:
+    elasticsearch_image: "{{ elasticsearch.docker_image | default('docker.elastic.co/elasticsearch/elasticsearch:' ~ elasticsearch.version ) }}"
+  docker_container:
+    name: "{{ elasticsearch_name }}"
+    image: "{{ elasticsearch_image }}"
+    keep_volumes: False
+    state: absent
+  ignore_errors: True
+
+- name: remove ElasticSearch conf dir
+  file:
+    path: "{{ db.elasticsearch.confdir }}/{{ elasticsearch_name }}"
+    state: absent
+  become: "{{ db.elasticsearch.dir.become }}"
diff --git a/openwhisk/ansible/roles/elasticsearch/tasks/deploy.yml b/openwhisk/ansible/roles/elasticsearch/tasks/deploy.yml
new file mode 100644
index 0000000..0194081
--- /dev/null
+++ b/openwhisk/ansible/roles/elasticsearch/tasks/deploy.yml
@@ -0,0 +1,94 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will run a ElasticSearch server on the db group
+
+- name: set the vm.max_map_count to 262144
+  sysctl:
+    name: vm.max_map_count
+    value: '262144'
+  become: true
+
+- name: set elasticsearch container name, volume and port
+  set_fact:
+    elasticsearch_name: "{{ name_prefix ~ host_group.index(inventory_hostname) }}"
+    volume_name: "{{ db.elasticsearch.base_volume ~ host_group.index(inventory_hostname) }}"
+    http_port: "{{ (db.elasticsearch.port|int) + host_group.index(inventory_hostname) }}"
+    transport_port: "{{ (db.elasticsearch.base_transport_port|int) + host_group.index(inventory_hostname) }}"
+
+- name: ensure elasticserach config directory is created with permissions
+  file:
+    path: "{{ db.elasticsearch.confdir }}/{{ elasticsearch_name }}"
+    state: directory
+    mode: 0755
+  become: "{{ db.elasticsearch.dir.become }}"
+
+# create volume directory if it's a directory path(not a named volume)
+- name: ensure elasticserach volume directory is created with permissions
+  file:
+    path: "{{ volume_name }}"
+    state: directory
+    mode: 0700
+    owner: "{{ db.elasticsearch.uid }}"
+  become: true
+  when: volume_name is search("/")
+
+- name: copy elasticsearch config file
+  template:
+    src: "elasticsearch.yml.j2"
+    dest: "{{ db.elasticsearch.confdir }}/{{ elasticsearch_name }}/elasticsearch.yml"
+    mode: 0644
+  become: "{{ db.elasticsearch.dir.become }}"
+
+- name: copy elasticsearch log config file
+  template:
+    src: "log4j2.properties.j2"
+    dest: "{{ db.elasticsearch.confdir }}/{{ elasticsearch_name }}/log4j2.properties"
+    mode: 0644
+  become: "{{ db.elasticsearch.dir.become }}"
+
+- name: "(re)start ElasticSearch from '{{ elasticsearch_image }} ' "
+  vars:
+    elasticsearch_image: "{{ elasticsearch.docker_image | default('docker.elastic.co/elasticsearch/elasticsearch:' ~ elasticsearch.version ) }}"
+  docker_container:
+    name: "{{ elasticsearch_name }}"
+    image: "{{ elasticsearch_image }}"
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    ports:
+      - "{{ http_port }}:9200"
+      - "{{ transport_port }}:{{ transport_port }}"
+    volumes:
+      - "{{ db.elasticsearch.confdir }}/{{ elasticsearch_name }}/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml"
+      - "{{ db.elasticsearch.confdir }}/{{ elasticsearch_name }}/log4j2.properties:/usr/share/elasticsearch/config/log4j2.properties"
+      - "{{ volume_name }}:/usr/share/elasticsearch/data"
+    pull: "{{ docker.pull_elasticsearch | default(true) }}"
+    ulimits:
+      - "nofile:262144:262144"
+      - "memlock:-1:-1"
+    env:
+      TZ: "{{ docker.timezone }}"
+      ES_JAVA_OPTS: "{{ db.elasticsearch.java_opts }}"
+
+- name: wait until ElasticSearch in this host is up and running
+  uri:
+    url: "{{ db.elasticsearch.protocol }}://{{ ansible_host }}:{{ http_port }}"
+  register: result
+  until: result.status == 200
+  retries: 12
+  delay: 5
diff --git a/openwhisk/ansible/roles/elasticsearch/tasks/main.yml b/openwhisk/ansible/roles/elasticsearch/tasks/main.yml
new file mode 100644
index 0000000..e5cc947
--- /dev/null
+++ b/openwhisk/ansible/roles/elasticsearch/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will deploy a database server. Use the role if you want to use ElasticSearch locally.
+# In deploy mode it will start the ElasticSearch container.
+# In clean mode it will remove the ElasticSearch container.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/elasticsearch/templates/elasticsearch.yml.j2 b/openwhisk/ansible/roles/elasticsearch/templates/elasticsearch.yml.j2
new file mode 100644
index 0000000..c300a5b
--- /dev/null
+++ b/openwhisk/ansible/roles/elasticsearch/templates/elasticsearch.yml.j2
@@ -0,0 +1,23 @@
+cluster.name: "{{ db.elasticsearch.cluster_name }}"
+node.name: "{{ elasticsearch_name }}"
+network.host: 0.0.0.0
+network.publish_host: {{ ansible_default_ipv4.address }}
+
+http.port: 9200
+transport.tcp.port: {{ transport_port }}
+
+# minimum_master_nodes need to be explicitly set when bound on a public IP
+# set to 1 to allow single node clusters
+# Details: https://github.com/elastic/elasticsearch/pull/17282
+discovery.zen.ping.unicast.hosts:
+{% for es in groups['elasticsearch'] %}
+   - {{ hostvars[es].ansible_host }}:{{ db.elasticsearch.base_transport_port|int + host_group.index(es)|int }}
+{% endfor %}
+discovery.zen.minimum_master_nodes: {{ (host_group|length / 2 + 1) | int}}
+
+gateway.recover_after_nodes: {{ (host_group|length / 2 + 1) | int }}
+gateway.expected_nodes: {{ host_group|length }}
+gateway.recover_after_time: 5m
+
+xpack.security.enabled: false
+bootstrap.memory_lock: true
diff --git a/openwhisk/ansible/roles/elasticsearch/templates/log4j2.properties.j2 b/openwhisk/ansible/roles/elasticsearch/templates/log4j2.properties.j2
new file mode 100644
index 0000000..cc8edde
--- /dev/null
+++ b/openwhisk/ansible/roles/elasticsearch/templates/log4j2.properties.j2
@@ -0,0 +1,10 @@
+status = error
+
+appender.console.type = Console
+appender.console.name = console
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
+
+rootLogger.appenderRef.console.ref = console
+
+rootLogger.level = {{ db.elasticsearch.loglevel }}
diff --git a/openwhisk/ansible/roles/etcd/tasks/clean.yml b/openwhisk/ansible/roles/etcd/tasks/clean.yml
new file mode 100644
index 0000000..418072b
--- /dev/null
+++ b/openwhisk/ansible/roles/etcd/tasks/clean.yml
@@ -0,0 +1,25 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove etcd containers.
+
+- name: remove etcd
+  docker_container:
+    name: etcd{{ groups['etcd'].index(inventory_hostname) }}
+    keep_volumes: True
+    state: absent
+  ignore_errors: True
diff --git a/openwhisk/ansible/roles/etcd/tasks/deploy.yml b/openwhisk/ansible/roles/etcd/tasks/deploy.yml
new file mode 100644
index 0000000..63b908c
--- /dev/null
+++ b/openwhisk/ansible/roles/etcd/tasks/deploy.yml
@@ -0,0 +1,70 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install etcd in group 'etcd' in the environment inventory
+
+- name: "Set the name of the etcd node"
+  set_fact:
+    name: "etcd{{ groups['etcd'].index(inventory_hostname) }}"
+
+- name: "set the volume_dir"
+  set_fact:
+    volume_dir: "{{ etcd.dir.data }}/etcd{{ groups['etcd'].index(inventory_hostname) }}:/etcd-data"
+  when: etcd_data_dir is defined
+
+
+- name: "Set the cluster of the etcd cluster"
+  set_fact:
+    cluster: "{% set etcdhosts = [] %}
+              {% for host in groups['etcd'] %}
+                  {{ etcdhosts.append('etcd' + ((loop.index-1)|string) + '=' + 'http://' + hostvars[host].ansible_host + ':' + ((2480+loop.index-1)|string) ) }}
+              {% endfor %}
+              {{ etcdhosts | join(',') }}"
+
+- name: (re)start etcd
+  docker_container:
+    name: etcd{{ groups['etcd'].index(inventory_hostname) }}
+    image: quay.io/coreos/etcd:{{ etcd.version }}
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    volumes: "{{volume_dir | default([])}}"
+    command: "/usr/local/bin/etcd \
+               --data-dir=/etcd-data --name '{{ name }}' \
+               --initial-advertise-peer-urls http://{{ ansible_host }}:{{ etcd.server.port + groups['etcd'].index(inventory_hostname) }} \
+               --advertise-client-urls http://{{ ansible_host }}:{{ etcd.client.port + groups['etcd'].index(inventory_hostname) }} \
+               --listen-peer-urls http://0.0.0.0:{{ etcd.server.port + groups['etcd'].index(inventory_hostname) }} \
+               --listen-client-urls http://0.0.0.0:{{ etcd.client.port + groups['etcd'].index(inventory_hostname) }} \
+               --initial-cluster {{ cluster }} \
+               --initial-cluster-state new --initial-cluster-token {{ etcd.cluster.token }} \
+               --quota-backend-bytes {{ etcd.quota_backend_bytes }} \
+               --snapshot-count {{ etcd.snapshot_count }} \
+               --auto-compaction-retention {{ etcd.auto_compaction_retention }} \
+               --auto-compaction-mode {{ etcd.auto_compaction_mode }} \
+               --log-level {{ etcd.loglevel }}"
+    ports:
+      - "{{ etcd.client.port + groups['etcd'].index(inventory_hostname) }}:{{ etcd.client.port + groups['etcd'].index(inventory_hostname) }}"
+      - "{{ etcd.server.port + groups['etcd'].index(inventory_hostname) }}:{{ etcd.server.port + groups['etcd'].index(inventory_hostname) }}"
+    pull: "{{ etcd.pull_etcd | default(true) }}"
+
+- name: wait until etcd in this host is up and running
+  uri:
+    url: "http://{{ ansible_host }}:{{ etcd.client.port + groups['etcd'].index(inventory_hostname) }}/health"
+  register: result
+  until: result.status == 200
+  retries: 12
+  delay: 5
diff --git a/openwhisk/ansible/roles/etcd/tasks/main.yml b/openwhisk/ansible/roles/etcd/tasks/main.yml
new file mode 100644
index 0000000..48505a8
--- /dev/null
+++ b/openwhisk/ansible/roles/etcd/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install etcd in group 'etcd' in the environment inventory
+# In deploy mode it will deploy etcd containers.
+# In clean mode it will remove etcd containers.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/invoker/tasks/clean.yml b/openwhisk/ansible/roles/invoker/tasks/clean.yml
new file mode 100644
index 0000000..b8f0b2f
--- /dev/null
+++ b/openwhisk/ansible/roles/invoker/tasks/clean.yml
@@ -0,0 +1,112 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove invoker containers.
+
+- name: get invoker name and index
+  set_fact:
+    invoker_name: "{{ name_prefix ~ ((invoker_index_base | int) + host_group.index(inventory_hostname)) }}"
+    invoker_index: "{{ (invoker_index_base | int) + host_group.index(inventory_hostname) }}"
+
+- name: disable invoker{{ groups['invokers'].index(inventory_hostname) }}
+  uri:
+    url: "{{ invoker.protocol }}://{{ ansible_host }}:{{ invoker.port + groups['invokers'].index(inventory_hostname) }}/disable"
+    validate_certs: no
+    client_key: "{{ invoker.confdir }}/invoker{{ groups['invokers'].index(inventory_hostname) }}/{{ invoker.ssl.key }}"
+    client_cert: "{{ invoker.confdir }}/invoker{{ groups['invokers'].index(inventory_hostname) }}/{{ invoker.ssl.cert }}"
+    method: POST
+    status_code: 200
+    user: "{{ invoker.username }}"
+    password: "{{ invoker.password }}"
+    force_basic_auth: yes
+  ignore_errors: "{{ invoker.deployment.ignore_error }}"
+  when: zeroDowntimeDeployment.enabled == true and enable_scheduler
+
+- name: wait invoker{{ groups['invokers'].index(inventory_hostname) }} to clean up all existing containers
+  uri:
+    url: "{{ invoker.protocol }}://{{ ansible_host }}:{{ invoker.port + groups['invokers'].index(inventory_hostname) }}/pool/count"
+    validate_certs: no
+    client_key: "{{ invoker.confdir }}/invoker{{ groups['invokers'].index(inventory_hostname) }}/{{ invoker.ssl.key }}"
+    client_cert: "{{ invoker.confdir }}/invoker{{ groups['invokers'].index(inventory_hostname) }}/{{ invoker.ssl.cert }}"
+    user: "{{ invoker.username }}"
+    password: "{{ invoker.password }}"
+    force_basic_auth: yes
+    return_content: yes
+  register: result
+  until: result.content == '0'
+  retries: "{{ invoker.deployment.retries }}"
+  delay: "{{ invoker.deployment.delay }}"
+  when: zeroDowntimeDeployment.enabled == true and enable_scheduler
+  ignore_errors: "{{ invoker.deployment.ignore_error }}"
+
+- name: remove invoker
+  docker_container:
+    name: "{{ invoker_name }}"
+    image: "{{ docker_registry }}{{ docker.image.prefix }}/invoker:{{ docker.image.tag }}"
+    state: absent
+    stop_timeout: 60
+    timeout: 120
+  ignore_errors: True
+
+# In case the invoker could not clean up completely in time.
+- name: pause/resume at runc-level to restore docker consistency
+  shell: |
+        DOCKER_PAUSED=$(docker ps --filter status=paused --filter name=wsk{{ invoker_index }} -q --no-trunc)
+        for C in $DOCKER_PAUSED; do docker-runc --root {{ invoker.docker.runcdir }} pause $C; done
+        DOCKER_RUNNING=$(docker ps --filter status=running --filter name=wsk{{ invoker_index }} -q --no-trunc)
+        for C2 in $DOCKER_RUNNING; do docker-runc --root {{ invoker.docker.runcdir }} resume $C2; done
+        TOTAL=$(($(echo $DOCKER_PAUSED | wc -w)+$(echo $DOCKER_RUNNING | wc -w)))
+        echo "Handled $TOTAL remaining actions."
+  register: runc_output
+  ignore_errors: True
+  become: "{{ invoker.docker.become }}"
+
+- debug: msg="{{ runc_output.stdout }}"
+
+- name: unpause remaining actions
+  shell: "docker unpause $(docker ps -aq --filter status=paused --filter name=wsk{{ invoker_index }})"
+  failed_when: False
+
+- name: remove remaining actions
+  shell: "docker rm -f $(docker ps -aq --filter name=wsk{{ invoker_index }})"
+  failed_when: False
+
+- name: remove invoker log directory
+  file:
+    path: "{{ whisk_logs_dir }}/{{ invoker_name }}"
+    state: absent
+  become: "{{ logs.dir.become }}"
+  when: mode == "clean"
+
+- name: remove invoker conf directory
+  file:
+    path: "{{ invoker.confdir }}/{{ invoker_name }}"
+    state: absent
+  become: "{{ invoker.dir.become }}"
+  when: mode == "clean"
+
+# Workaround for orphaned ifstate.veth* files on Ubuntu 14.04
+# See https://github.com/moby/moby/issues/22513
+# Remove inactive files older than 60 minutes
+- name: "Clean orphaned ifstate.veth* files on Ubuntu 14.04"
+  shell: |
+    ACTIVE_VETH_IFACES=$(ip -oneline link show | grep --only-matching --extended-regexp 'veth[0-9a-f]+' | tr '\n' '|' | sed -e 's/.$//')
+    EXCLUDE_REGEX=$(if [ -z ${ACTIVE_VETH_IFACES} ]; then echo 'No active veth interfaces found' >&2; else printf '( -not -regex  /run/network/ifstate\.(%s) ) -and ' ${ACTIVE_VETH_IFACES}; fi)
+    find /run/network -regextype posix-egrep ${EXCLUDE_REGEX} -name 'ifstate.veth*' -and -mmin +60 -delete
+  become: True
+  ignore_errors: True
+  when: ansible_distribution == 'Ubuntu' and ansible_distribution_version == '14.04'
diff --git a/openwhisk/ansible/roles/invoker/tasks/deploy.yml b/openwhisk/ansible/roles/invoker/tasks/deploy.yml
new file mode 100644
index 0000000..62e21fb
--- /dev/null
+++ b/openwhisk/ansible/roles/invoker/tasks/deploy.yml
@@ -0,0 +1,452 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role installs invokers.
+
+###
+# When the zero-downtime-deployment is enabled, clean.yml is used to gracefully shut down the invoker.
+#
+- import_tasks: clean.yml
+  when: zeroDowntimeDeployment.enabled == true and enable_scheduler
+
+- import_tasks: docker_login.yml
+
+- name: get invoker name and index
+  set_fact:
+    invoker_name: "{{ name_prefix ~ ((invoker_index_base | int) + host_group.index(inventory_hostname)) }}"
+    invoker_index: "{{ (invoker_index_base | int) + host_group.index(inventory_hostname) }}"
+
+- name: "pull invoker image with tag {{docker.image.tag}}"
+  shell: "docker pull {{docker_registry}}{{ docker.image.prefix }}/invoker:{{docker.image.tag}}"
+  when: docker_registry != ""
+  register: result
+  until: (result.rc == 0)
+  retries: "{{ docker.pull.retries }}"
+  delay: "{{ docker.pull.delay }}"
+
+###
+# This task assumes that the images are local to the invoker host already if there is no prefix or tag
+# which is usually the case for a local deployment. A distributed deployment will specify the prefix, or tag
+# to pull the images from the appropriate registry. If a runtimes_registry is optionally specified, pull images
+# from there; this permits a (private) registry to be used for caching the images. The registry if specified
+# must include a trailing '/'.
+#
+- name: "pull runtime action images per manifest"
+  shell: "docker pull {{runtimes_registry | default()}}{{inv_item.prefix}}/{{inv_item.name}}:{{inv_item.tag | default()}}"
+  loop: "{{ runtimesManifest.runtimes.values() | sum(start=[]) | selectattr('deprecated', 'equalto',false)  | map(attribute='image') | list | unique }}"
+  when: skip_pull_runtimes is not defined or not (skip_pull_runtimes == True or skip_pull_runtimes.lower() == "true")
+  register: result
+  until: (result.rc == 0)
+  retries: "{{ docker.pull.retries }}"
+  delay: "{{ docker.pull.delay }}"
+  loop_control:
+    loop_var: inv_item
+
+###
+# See comment above for pulling other runtime images.
+#
+- name: "pull blackboxes action images per manifest"
+  shell: "docker pull {{runtimes_registry | default()}}{{inv_item.prefix}}/{{inv_item.name}}:{{inv_item.tag | default()}}"
+  loop: "{{ runtimesManifest.blackboxes }}"
+  when: skip_pull_runtimes is not defined or not (skip_pull_runtimes == True or skip_pull_runtimes.lower() == "true")
+  register: result
+  until: (result.rc == 0)
+  retries: "{{ docker.pull.retries }}"
+  delay: "{{ docker.pull.delay }}"
+  loop_control:
+    loop_var: inv_item
+
+- name: "determine docker root dir on docker-machine"
+  uri:  url="http://{{ ansible_host }}:{{ docker.port }}/info" return_content=yes
+  register: dockerInfo_output
+  when: environmentInformation.type == 'docker-machine'
+
+- set_fact:
+    dockerInfo: "{{ dockerInfo_output['json'] }}"
+  when: environmentInformation.type == "docker-machine"
+
+- name: "determine docker root dir"
+  shell: echo -e "GET http:/v1.24/info HTTP/1.0\r\n" | nc -U /var/run/docker.sock | grep "{"
+  args:
+    executable: /bin/bash
+  register: dockerInfo_output
+  when: environmentInformation.type != "docker-machine"
+
+- set_fact:
+    dockerInfo: "{{ dockerInfo_output.stdout|from_json }}"
+  when: environmentInformation.type != "docker-machine"
+
+- name: ensure invoker log directory is created with permissions
+  file:
+    path: "{{ whisk_logs_dir }}/{{ invoker_name }}"
+    state: directory
+    mode: 0777
+  become: "{{ logs.dir.become }}"
+
+- name: ensure invoker config directory is created with permissions
+  file:
+    path: "{{ invoker.confdir }}/{{ invoker_name }}"
+    state: directory
+    mode: 0777
+  become: "{{ invoker.dir.become }}"
+
+- name: "copy kafka truststore/keystore"
+  when: kafka.protocol == 'SSL'
+  copy:
+    src: "{{ openwhisk_home }}/ansible/roles/kafka/files/{{ kafka.ssl.keystore.name }}"
+    dest: "{{ invoker.confdir }}/{{ invoker_name }}"
+
+- name: copy keystore, key and cert
+  when: invoker.protocol == "https"
+  copy:
+    src: "{{ inv_item }}"
+    mode: 0666
+    dest: "{{ invoker.confdir }}/{{ invoker_name }}"
+  become: "{{ invoker.dir.become }}"
+  with_items:
+  - "{{ openwhisk_home }}/ansible/roles/invoker/files/{{ invoker.ssl.keystore.name }}"
+  - "{{ openwhisk_home }}/ansible/roles/invoker/files/{{ invoker.ssl.key }}"
+  - "{{ openwhisk_home }}/ansible/roles/invoker/files/{{ invoker.ssl.cert }}"
+  loop_control:
+    loop_var: inv_item
+
+- name: check, that required databases exist
+  include_tasks: "{{ openwhisk_home }}/ansible/tasks/db/checkDb.yml"
+  vars:
+    dbName: "{{ inv_item }}"
+    dbUser: "{{ db.credentials.invoker.user }}"
+    dbPass: "{{ db.credentials.invoker.pass }}"
+  with_items:
+  - "{{ db.whisk.actions }}"
+  - "{{ db.whisk.activations }}"
+  loop_control:
+    loop_var: inv_item
+
+- name: get running invoker information
+  uri: url="http://{{ ansible_host }}:{{ docker.port }}/containers/json?filters={{ '{"name":[ "invoker" ],"ancestor":[ "invoker" ]}' | urlencode }}" return_content=yes
+  register: invokerInfo_output
+  when: environmentInformation.type == "docker-machine"
+
+- set_fact:
+    invokerInfo: "{{ invokerInfo_output['json'] }}"
+  when: environmentInformation.type == "docker-machine"
+
+- name: "get invoker info"
+  shell: |
+    INFO=`echo -e "GET http:/v1.24/containers/json?filters={{ '{"name":[ "invoker" ],"ancestor":[ "invoker" ]}' | urlencode }} HTTP/1.0\r\n" | nc -U /var/run/docker.sock | grep "{"`
+    if [ -z "$INFO" ]; then
+      echo []
+    else
+      echo $INFO
+    fi
+  args:
+    executable: /bin/bash
+  register: invokerInfo_output
+  when: environmentInformation.type != "docker-machine"
+
+- set_fact:
+    invokerInfo: "{{ invokerInfo_output.stdout|from_json }}"
+  when: environmentInformation.type != "docker-machine"
+
+- name: determine if more than one invoker is running
+  fail:
+    msg: "more than one invoker is running"
+  when: not invoker.allowMultipleInstances and invokerInfo|length > 1
+
+- name: determine if index of invoker is same with index of inventory host
+  fail:
+    msg: "invoker index is invalid. expected: /invoker{{ groups['invokers'].index(inventory_hostname) }} found: {{ inv_item.Names[0] }}"
+  with_items: "{{ invokerInfo }}"
+  when: not invoker.allowMultipleInstances and inv_item.Names[0] != "/{{ invoker_name }}"
+  loop_control:
+    loop_var: inv_item
+
+- name: copy jmxremote password file
+  when: jmx.enabled
+  template:
+    src: "jmxremote.password.j2"
+    dest: "{{ invoker.confdir  }}/{{ invoker_name }}/jmxremote.password"
+    mode: 0777
+
+- name: copy jmxremote access file
+  when: jmx.enabled
+  template:
+    src: "jmxremote.access.j2"
+    dest: "{{ invoker.confdir  }}/{{ invoker_name }}/jmxremote.access"
+    mode: 0777
+
+- name: add additional jvm params if jmxremote is enabled
+  when: jmx.enabled
+  set_fact:
+    invoker_args: "{{ invoker.arguments }} {{ invoker.jmxremote.jvmArgs }}"
+
+- name: prepare invoker ports
+  set_fact:
+    invoker_ports_to_expose: ["{{ invoker.port + (invoker_index | int) }}:8080"]
+
+- name: expose additional ports if jmxremote is enabled
+  when: jmx.enabled
+  set_fact:
+    invoker_ports_to_expose: "{{ invoker_ports_to_expose }} + [ \"{{ jmx.basePortInvoker + (invoker_index | int) }}:{{ jmx.basePortInvoker + (invoker_index | int) }}\" ] + [ \"{{ jmx.rmiBasePortInvoker + (invoker_index | int) }}:{{ jmx.rmiBasePortInvoker + (invoker_index | int) }}\" ]"
+
+
+- name: Load config from template
+  set_fact:
+    openwhisk_config: "{{ lookup('template', 'config.j2') | b64encode }}"
+
+- name: populate environment variables for invoker
+  set_fact:
+    env:
+      "JAVA_OPTS": "-Xmx{{ invoker.heap }} -XX:+CrashOnOutOfMemoryError -XX:+UseGCOverheadLimit -XX:ErrorFile=/logs/java_error.log"
+      "INVOKER_OPTS": "{{ invoker_args | default(invoker.arguments) }}"
+      "JMX_REMOTE": "{{ jmx.enabled }}"
+      "OPENWHISK_ENCODED_CONFIG": "{{ openwhisk_config }}"
+      "PORT": "8080"
+      "TZ": "{{ docker.timezone }}"
+      "KAFKA_HOSTS": "{{ kafka_connect_string }}"
+      "CONFIG_whisk_kafka_replicationFactor": "{{ kafka.replicationFactor | default() }}"
+      "CONFIG_whisk_kafka_topics_invoker_retentionBytes": "{{ kafka_topics_invoker_retentionBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_invoker_retentionMs": "{{ kafka_topics_invoker_retentionMS | default() }}"
+      "CONFIG_whisk_kakfa_topics_invoker_segmentBytes": "{{ kafka_topics_invoker_segmentBytes | default() }}"
+      "CONFIG_whisk_kafka_topics_prefix": "{{ kafka.topicsPrefix }}"
+      "CONFIG_whisk_kafka_topics_userEvent_prefix": "{{ kafka.topicsUserEventPrefix }}"
+      "CONFIG_whisk_kafka_common_securityProtocol": "{{ kafka.protocol }}"
+      "CONFIG_whisk_kafka_common_sslTruststoreLocation": "/conf/{{ kafka.ssl.keystore.name }}"
+      "CONFIG_whisk_kafka_common_sslTruststorePassword": "{{ kafka.ssl.keystore.password }}"
+      "CONFIG_whisk_kafka_common_sslKeystoreLocation": "/conf/{{ kafka.ssl.keystore.name }}"
+      "CONFIG_whisk_kafka_common_sslKeystorePassword": "{{ kafka.ssl.keystore.password }}"
+      "CONFIG_whisk_userEvents_enabled": "{{ user_events | default(false) | lower }}"
+      "ZOOKEEPER_HOSTS": "{{ zookeeper_connect_string }}"
+      "CONFIG_whisk_couchdb_protocol": "{{ db.protocol }}"
+      "CONFIG_whisk_couchdb_host": "{{ db.host }}"
+      "CONFIG_whisk_couchdb_port": "{{ db.port }}"
+      "CONFIG_whisk_couchdb_username": "{{ db.credentials.invoker.user }}"
+      "CONFIG_whisk_couchdb_password": "{{ db.credentials.invoker.pass }}"
+      "CONFIG_whisk_couchdb_provider": "{{ db.provider }}"
+      "CONFIG_whisk_couchdb_databases_WhiskAuth": "{{ db.whisk.auth }}"
+      "CONFIG_whisk_couchdb_databases_WhiskEntity": "{{ db.whisk.actions }}"
+      "CONFIG_whisk_couchdb_databases_WhiskActivation": "{{ db.whisk.activations }}"
+      "DB_WHISK_ACTIONS": "{{ db.whisk.actions }}"
+      "DB_WHISK_ACTIVATIONS": "{{ db.whisk.activations }}"
+      "DB_WHISK_AUTHS": "{{ db.whisk.auth }}"
+      "CONFIG_whisk_db_subjectsDdoc": "{{ db_whisk_subjects_ddoc | default() }}"
+      "CONFIG_whisk_db_actionsDdoc": "{{ db_whisk_actions_ddoc | default() }}"
+      "CONFIG_whisk_db_activationsDdoc": "{{ db_whisk_activations_ddoc | default() }}"
+      "CONFIG_whisk_db_activationsFilterDdoc": "{{ db_whisk_activations_filter_ddoc | default() }}"
+      "WHISK_API_HOST_PROTO": "{{ whisk_api_host_proto | default('https') }}"
+      "WHISK_API_HOST_PORT": "{{ whisk_api_host_port | default('443') }}"
+      "WHISK_API_HOST_NAME": "{{ whisk_api_host_name | default(groups['edge'] | first) }}"
+      "CONFIG_whisk_containerFactory_runtimesRegistry_url": "{{ runtimes_registry | default('') }}"
+      "CONFIG_whisk_containerFactory_userImagesRegistry_url": "{{ user_images_registry | default('') }}"
+      "RUNTIMES_MANIFEST": "{{ runtimesManifest | to_json }}"
+      "CONFIG_whisk_runtimes_bypassPullForLocalImages": "{{ runtimes_bypass_pull_for_local_images | default() | lower }}"
+      "CONFIG_whisk_runtimes_localImagePrefix": "{{ runtimes_local_image_prefix | default() }}"
+      "CONFIG_whisk_containerFactory_containerArgs_network": "{{ invoker_container_network_name | default('bridge') }}"
+      "INVOKER_CONTAINER_POLICY": "{{ invoker_container_policy_name | default()}}"
+      "CONFIG_whisk_containerPool_userMemory": "{{ hostvars[groups['invokers'][invoker_index | int]].user_memory | default(invoker.userMemory) }}"
+      "CONFIG_whisk_docker_client_parallelRuns": "{{ invoker_parallel_runs | default() }}"
+      "CONFIG_whisk_docker_containerFactory_useRunc": "{{ invoker.useRunc | default(false) | lower }}"
+      "WHISK_LOGS_DIR": "{{ whisk_logs_dir }}"
+      "METRICS_KAMON": "{{ metrics.kamon.enabled | default(false) | lower }}"
+      "METRICS_KAMON_TAGS": "{{ metrics.kamon.tags | default() | lower }}"
+      "METRICS_LOG": "{{ metrics.log.enabled | default(false) | lower }}"
+      "CONFIG_kamon_statsd_hostname": "{{ metrics.kamon.host }}"
+      "CONFIG_kamon_statsd_port": "{{ metrics.kamon.port }}"
+      "CONFIG_whisk_spi_LogStoreProvider": "{{ userLogs.spi }}"
+      "CONFIG_whisk_spi_InvokerProvider": "{{ invoker.reactiveSpi }}"
+      "CONFIG_whisk_spi_InvokerServerProvider": "{{ invoker.serverSpi }}"
+      "CONFIG_logback_log_level": "{{ invoker.loglevel }}"
+      "CONFIG_whisk_memory_min": "{{ limit_action_memory_min | default() }}"
+      "CONFIG_whisk_memory_max": "{{ limit_action_memory_max | default() }}"
+      "CONFIG_whisk_memory_std": "{{ limit_action_memory_std | default() }}"
+      "CONFIG_whisk_timeLimit_min": "{{ limit_action_time_min | default() }}"
+      "CONFIG_whisk_timeLimit_max": "{{ limit_action_time_max | default() }}"
+      "CONFIG_whisk_timeLimit_std": "{{ limit_action_time_std | default() }}"
+      "CONFIG_whisk_concurrencyLimit_min": "{{ limit_action_concurrency_min | default() }}"
+      "CONFIG_whisk_concurrencyLimit_max": "{{ limit_action_concurrency_max | default() }}"
+      "CONFIG_whisk_concurrencyLimit_std": "{{ limit_action_concurrency_std | default() }}"
+      "CONFIG_whisk_activation_payload_max": "{{ limit_activation_payload | default() }}"
+      "CONFIG_whisk_transactions_header": "{{ transactions.header }}"
+      "CONFIG_whisk_containerPool_akkaClient": "{{ container_pool_akka_client | default('false') | lower }}"
+      "CONFIG_whisk_containerFactory_containerArgs_extraEnvVars_0": "__OW_ALLOW_CONCURRENT={{ runtimes_enable_concurrency | default('false') }}"
+      "CONFIG_whisk_invoker_protocol": "{{ invoker.protocol }}"
+      "CONFIG_whisk_invoker_https_keystorePath": "/conf/{{ invoker.ssl.keystore.name }}"
+      "CONFIG_whisk_invoker_https_keystorePassword": "{{ invoker.ssl.keystore.password }}"
+      "CONFIG_whisk_invoker_https_keystoreFlavor": "{{ invoker.ssl.storeFlavor }}"
+      "CONFIG_whisk_invoker_https_clientAuth": "{{ invoker.ssl.clientAuth }}"
+      "CONFIG_whisk_invoker_resource_tags": "{% if tags is defined %} '{{ tags | join(',') }}' {% else %} '' {% endif %}"
+      "CONFIG_whisk_invoker_dedicated_namespaces": "{% if dedicatedNamespaces is defined %} '{{ dedicatedNamespaces | join(',') }}' {% else %} '' {% endif %}"
+      "CONFIG_whisk_containerProxy_timeouts_idleContainer": "{{ whisk.containerProxy.timeouts.idleContainer }}"
+      "CONFIG_whisk_containerProxy_timeouts_pauseGrace": "{{ whisk.containerProxy.timeouts.pauseGrace  }}"
+      "CONFIG_whisk_containerProxy_timeouts_keepingDuration": "{{ whisk.containerProxy.timeouts.keepingDuration }}"
+      "CONFIG_whisk_containerPool_prewarmExpirationCheckInitDelay": "{{ container_pool_prewarm_expirationCheckInitDelay | default('10 minutes') }}"
+      "CONFIG_whisk_containerPool_prewarmExpirationCheckInterval": "{{ container_pool_prewarm_expirationCheckInterval | default('10 minutes') }}"
+      "CONFIG_whisk_containerPool_prewarmExpirationCheckIntervalVariance": "{{ container_pool_prewarm_expirationCheckIntervalVariance | default('10 seconds') }}"
+      "CONFIG_whisk_containerPool_prewarmPromotion": "{{ container_pool_strict | default('false') | lower }}"
+      "CONFIG_whisk_containerPool_prewarmMaxRetryLimit": "{{ container_pool_prewarm_max_retry_limit | default(5) }}"
+      "CONFIG_whisk_containerPool_memorySyncInterval": "{{ container_pool_memorySyncInterval | default('1 second') }}"
+      "CONFIG_whisk_containerPool_batchDeletionSize": "{{ container_pool_batchDeletionSize | default(10) }}"
+      "CONFIG_whisk_invoker_username": "{{ invoker.username }}"
+      "CONFIG_whisk_invoker_password": "{{ invoker.password }}"
+      "CONFIG_whisk_cluster_name": "{{ whisk.cluster_name | lower }}"
+
+- name: extend invoker dns env
+  set_fact:
+    env: "{{ env | default({}) | combine( {'CONFIG_whisk_containerFactory_containerArgs_dnsServers_' ~ inv_item.0: inv_item.1} ) }}"
+  with_indexed_items: "{{ (invoker_container_network_dns_servers | default()).split(' ')}}"
+  loop_control:
+    loop_var: inv_item
+
+- name: merge extra env variables
+  set_fact:
+    env: "{{ env | combine(invoker.extraEnv) }}"
+
+- name: setup elasticsearch activation store env
+  set_fact:
+    elastic_env:
+      "CONFIG_whisk_activationStore_elasticsearch_protocol": "{{ db.elasticsearch.protocol}}"
+      "CONFIG_whisk_activationStore_elasticsearch_hosts": "{{ elasticsearch_connect_string }}"
+      "CONFIG_whisk_activationStore_elasticsearch_indexPattern": "{{ db.elasticsearch.index_pattern }}"
+      "CONFIG_whisk_activationStore_elasticsearch_username": "{{ db.elasticsearch.auth.admin.username }}"
+      "CONFIG_whisk_activationStore_elasticsearch_password": "{{ db.elasticsearch.auth.admin.password }}"
+      "CONFIG_whisk_spi_ActivationStoreProvider": "org.apache.openwhisk.core.database.elasticsearch.ElasticSearchActivationStoreProvider"
+  when: db.activation_store.backend == "ElasticSearch"
+
+- name: merge elasticsearch activation store env
+  set_fact:
+    env: "{{ env | combine(elastic_env) }}"
+  when: db.activation_store.backend == "ElasticSearch"
+
+- name: setup mongodb artifact store env
+  set_fact:
+    mongodb_env:
+      "CONFIG_whisk_mongodb_uri": "{{ db.mongodb.connect_string }}"
+      "CONFIG_whisk_mongodb_database": "{{ db.mongodb.database }}"
+      "CONFIG_whisk_spi_ArtifactStoreProvider": "org.apache.openwhisk.core.database.mongodb.MongoDBArtifactStoreProvider"
+  when: db.artifact_store.backend == "MongoDB"
+
+- name: merge mongodb artifact store env
+  set_fact:
+    env: "{{ env | combine(mongodb_env) }}"
+  when: db.artifact_store.backend == "MongoDB"
+
+- name: setup scheduler env
+  set_fact:
+    scheduler_env:
+      "CONFIG_whisk_etcd_hosts": "{{ etcd_connect_string }}"
+      "CONFIG_whisk_etcd_lease_timeout": "{{ etcd.lease.timeout }}"
+      "CONFIG_whisk_etcd_pool_threads": "{{ etcd.pool_threads }}"
+      "CONFIG_whisk_scheduler_dataManagementService_retryInterval": "{{ scheduler.dataManagementService.retryInterval }}"
+      "CONFIG_whisk_invoker_containerCreation_maxPeek": "{{ invoker.container.creationMaxPeek }}"
+      "CONFIG_whisk_spi_InvokerProvider": "org.apache.openwhisk.core.invoker.FPCInvokerReactive"
+      "CONFIG_whisk_spi_InvokerServerProvider": "org.apache.openwhisk.core.invoker.FPCInvokerServer"
+  when: enable_scheduler
+
+- name: merge scheduler env
+  set_fact:
+    env: "{{ env | combine(scheduler_env) }}"
+  when: enable_scheduler
+
+- name: include plugins
+  include_tasks: "{{ inv_item }}.yml"
+  with_items: "{{ invoker_plugins | default([]) }}"
+  loop_control:
+    loop_var: inv_item
+
+- name: set invoker volumes
+  set_fact:
+    volumes: "/sys/fs/cgroup:/sys/fs/cgroup,\
+      {{ whisk_logs_dir }}/{{ invoker_name }}:/logs,\
+      {{ invoker.confdir }}/{{ invoker_name }}:/conf,\
+      {{ dockerInfo['DockerRootDir'] }}/containers/:/containers,\
+      {{ docker_sock | default('/var/run/docker.sock') }}:/var/run/docker.sock"
+###
+# The root runc directory varies based on the version of docker and runc.
+# When docker>=18.06 uses docker-runc the directory is /run/docker/runtime-runc/moby.
+# While docker-runc itself uses /run/runc for a root user or /run/user/<uid>/runc for a non-root user.
+# Currently, the invoker is running as a root user so the below configuration works as expected.
+# But when the invoker needs to run as a non-root user or the version docker needs to be changed,
+# the following configuration should be properly updated as well.
+#
+# Alternatively, we can disable the runc with invoker.userRunc = false.
+#
+- name: set invoker runc volume
+  set_fact:
+    volumes: "{{ volumes }},{{ invoker.docker.runcdir }}:/run/runc"
+  when: invoker.useRunc == true
+
+- name: define options when deploying invoker on Ubuntu
+  set_fact:
+    volumes: "{{ volumes|default('') }},/usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1"
+  when: ansible_distribution == "Ubuntu"
+
+- name: check if coverage collection is enabled
+  set_fact:
+    coverage_enabled: false
+  when: coverage_enabled is undefined
+
+- name: ensure invoker coverage directory is created with permissions
+  file:
+    path: "{{ coverage_logs_dir }}/invoker/{{ inv_item }}"
+    state: directory
+    mode: 0777
+  with_items:
+    - invoker
+    - common
+  become: "{{ logs.dir.become }}"
+  when: coverage_enabled
+  loop_control:
+    loop_var: inv_item
+
+- name: extend invoker volume for coverage
+  set_fact:
+    volumes: "{{ volumes|default('') }},{{ coverage_logs_dir }}/invoker:/coverage"
+  when: coverage_enabled
+
+- name: set invoker docker volumes
+  set_fact:
+    volumes: "{{ volumes|default('') }},{{ invoker.docker.volumes | join(',') }}"
+  when: invoker.docker.volumes|length > 0
+
+- name: start invoker
+  docker_container:
+    userns_mode: "host"
+    pid_mode: "host"
+    privileged: "yes"
+    name: "{{ invoker_name }}"
+    hostname: "{{ invoker_name }}"
+    restart_policy: "{{ docker.restart.policy }}"
+    image: "{{ docker_registry }}{{ docker.image.prefix }}/invoker:{{ 'cov' if (coverage_enabled) else docker.image.tag }}"
+    state: started
+    recreate: true
+    env: "{{ env }}"
+    volumes: "{{ volumes }}"
+    ports: "{{ invoker_ports_to_expose }}"
+    command: /bin/sh -c "exec /init.sh --id {{ invoker_index }} --uniqueName {{ invoker_index }} >> /logs/{{ invoker_name }}_logs.log 2>&1"
+  when: not lean
+
+- name: wait until Invoker is up and running
+  uri:
+    url: "{{ invoker.protocol }}://{{ ansible_host }}:{{ invoker.port + (invoker_index | int) }}/ping"
+    validate_certs: "no"
+    client_key: "{{ invoker.confdir }}/{{ invoker_name }}/{{ invoker.ssl.key }}"
+    client_cert: "{{ invoker.confdir }}/{{ invoker_name }}/{{ invoker.ssl.cert }}"
+  register: result
+  until: result.status == 200
+  retries: 12
+  delay: 5
+  when: not lean
diff --git a/openwhisk/ansible/roles/invoker/tasks/main.yml b/openwhisk/ansible/roles/invoker/tasks/main.yml
new file mode 100644
index 0000000..8defa38
--- /dev/null
+++ b/openwhisk/ansible/roles/invoker/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install invoker in group 'invokers' in the environment inventory
+# In deploy mode it will deploy invokers.
+# In clean mode it will remove the invoker containers.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/invoker/templates/config.j2 b/openwhisk/ansible/roles/invoker/templates/config.j2
new file mode 100644
index 0000000..943769c
--- /dev/null
+++ b/openwhisk/ansible/roles/invoker/templates/config.j2
@@ -0,0 +1,8 @@
+include classpath("application.conf")
+
+# Specify any custom config here. For example
+# whisk {
+#   metrics {
+#     prometheus-enabled = true
+#   }
+# }
\ No newline at end of file
diff --git a/openwhisk/ansible/roles/kafka/tasks/clean.yml b/openwhisk/ansible/roles/kafka/tasks/clean.yml
new file mode 100644
index 0000000..b461427
--- /dev/null
+++ b/openwhisk/ansible/roles/kafka/tasks/clean.yml
@@ -0,0 +1,34 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove kafka and zookeeper containers.
+
+- name: remove old kafka
+  docker_container:
+    name: kafka
+    image: "{{ docker_registry }}{{ docker.image.prefix }}/kafka:{{ docker.image.tag }}"
+    keep_volumes: False
+    state: absent
+  ignore_errors: True
+
+- name: remove kafka
+  docker_container:
+    name: kafka{{ groups['kafkas'].index(inventory_hostname) }}
+    image: "{{ docker_registry }}{{ docker.image.prefix }}/kafka:{{ docker.image.tag }}"
+    keep_volumes: False
+    state: absent
+  ignore_errors: True
diff --git a/openwhisk/ansible/roles/kafka/tasks/deploy.yml b/openwhisk/ansible/roles/kafka/tasks/deploy.yml
new file mode 100644
index 0000000..6406990
--- /dev/null
+++ b/openwhisk/ansible/roles/kafka/tasks/deploy.yml
@@ -0,0 +1,110 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install Kafka with Zookeeper in group 'kafka' in the environment inventory
+
+- name: "create kafka certificate directory"
+  file:
+    path: "{{ config_root_dir }}/kafka/certs"
+    state: directory
+    mode: 0777
+
+- name: "copy keystore"
+  when: kafka.protocol == 'SSL'
+  copy:
+    src: "files/{{ kafka.ssl.keystore.name }}"
+    dest: "{{ config_root_dir }}/kafka/certs"
+
+- name: add kafka default env vars
+  set_fact:
+    kafka_env_vars:
+      "KAFKA_DEFAULT_REPLICATION_FACTOR": "{{ kafka.replicationFactor }}"
+      "KAFKA_BROKER_ID": "{{ groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_HEAP_OPTS": "-Xmx{{ kafka.heap }} -Xms{{ kafka.heap }}"
+      "KAFKA_ZOOKEEPER_CONNECT": "{{ zookeeper_connect_string }}"
+      "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "{{ kafka.offsetsTopicReplicationFactor }}"
+      "KAFKA_AUTO_CREATE_TOPICS_ENABLE": "false"
+      "KAFKA_NUM_NETWORK_THREADS": "{{ kafka.networkThreads }}"
+      "TZ": "{{ docker.timezone }}"
+
+- name: add kafka non-ssl vars
+  when: kafka.protocol != 'SSL'
+  set_fact:
+    kafka_non_ssl_vars:
+      "KAFKA_ADVERTISED_PORT": "{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_PORT": "{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "EXTERNAL:PLAINTEXT"
+      "KAFKA_LISTENERS": "EXTERNAL://:{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_ADVERTISED_LISTENERS": "EXTERNAL://{{ ansible_host }}:{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_INTER_BROKER_LISTENER_NAME": "EXTERNAL"
+
+- name: add kafka ssl env vars
+  when: kafka.protocol == 'SSL'
+  set_fact:
+    kafka_ssl_env_vars:
+      "KAFKA_ADVERTISED_PORT": "{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_PORT": "{{ kafka.port + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "INTERNAL:PLAINTEXT,EXTERNAL:SSL"
+      "KAFKA_LISTENERS": "EXTERNAL://:{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }},INTERNAL://:{{ kafka.port + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_ADVERTISED_LISTENERS": "EXTERNAL://{{ ansible_host }}:{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }},INTERNAL://{{ ansible_host }}:{{ kafka.port + groups['kafkas'].index(inventory_hostname) }}"
+      "KAFKA_INTER_BROKER_LISTENER_NAME": "INTERNAL"
+      "KAFKA_SSL_KEYSTORE_LOCATION": "/config/{{ kafka.ssl.keystore.name }}"
+      "KAFKA_SSL_KEYSTORE_PASSWORD": "{{ kafka.ssl.keystore.password }}"
+      "KAFKA_SSL_KEY_PASSWORD": "{{ kafka.ssl.keystore.password }}"
+      "KAFKA_SSL_TRUSTSTORE_LOCATION": "/config/{{ kafka.ssl.keystore.name }}"
+      "KAFKA_SSL_TRUSTSTORE_PASSWORD": "{{ kafka.ssl.keystore.password }}"
+      "KAFKA_SSL_CLIENT_AUTH": "{{ kafka.ssl.client_authentication }}"
+      "KAFKA_SSL_CIPHER_SUITES": "{{ kafka.ssl.cipher_suites | join(',') }}"
+      "KAFKA_SSL_ENABLED_PROTOCOLS": "{{ kafka.ssl.protocols | join(',') }}"
+
+- name: "join kafka ssl env vars"
+  when: kafka.protocol == 'SSL'
+  set_fact:
+    kafka_env_vars: "{{ kafka_env_vars | combine(kafka_ssl_env_vars) }}"
+
+- name: join kafka non-ssl env vars
+  when: kafka.protocol != 'SSL'
+  set_fact:
+    kafka_env_vars: "{{ kafka_env_vars | combine(kafka_non_ssl_vars) }}"
+
+- name: "(re)start kafka using '{{ kafka_image }}' "
+  vars:
+    zookeeper_idx: "{{ groups['kafkas'].index(inventory_hostname) % (groups['zookeepers'] | length) }}"
+    kafka_image: "{{ kafka.docker_image | default ('wurstmeister/kafka:' ~ kafka.version) }}"
+  docker_container:
+    name: kafka{{ groups['kafkas'].index(inventory_hostname) }}
+    image: "{{ kafka_image }}"
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    env: "{{ kafka_env_vars }}"
+    ports:
+      - "{{ kafka.port + groups['kafkas'].index(inventory_hostname) }}:{{ kafka.port + groups['kafkas'].index(inventory_hostname) }}"
+      - "{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}:{{ kafka.advertisedPort + groups['kafkas'].index(inventory_hostname) }}"
+    volumes:
+      - "{{ config_root_dir }}/kafka/certs:/config"
+    pull: "{{ kafka.pull_kafka | default(true) }}"
+
+- name: wait until the kafka server started up
+  shell:
+    (echo dump; sleep 1) |
+    nc {{hostvars[groups['zookeepers']|first].ansible_host}} {{zookeeper.port}} |
+    grep /brokers/ids/{{ groups['kafkas'].index(inventory_hostname) }}
+  register: result
+  until: (result.rc == 0)
+  retries: 10
+  delay: 5
diff --git a/openwhisk/ansible/roles/kafka/tasks/main.yml b/openwhisk/ansible/roles/kafka/tasks/main.yml
new file mode 100644
index 0000000..a5af4bc
--- /dev/null
+++ b/openwhisk/ansible/roles/kafka/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install kafka in group 'kafka' in the environment inventory
+# In deploy mode it will deploy kafka including zookeeper.
+# In clean mode it will remove kafka and zookeeper containers.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/mongodb/tasks/clean.yml b/openwhisk/ansible/roles/mongodb/tasks/clean.yml
new file mode 100644
index 0000000..248ee55
--- /dev/null
+++ b/openwhisk/ansible/roles/mongodb/tasks/clean.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove MongoDB server
+
+- name: remove MongoDB
+  docker_container:
+    name: mongodb
+    state: absent
+    keep_volumes: False
diff --git a/openwhisk/ansible/roles/mongodb/tasks/deploy.yml b/openwhisk/ansible/roles/mongodb/tasks/deploy.yml
new file mode 100644
index 0000000..14bf146
--- /dev/null
+++ b/openwhisk/ansible/roles/mongodb/tasks/deploy.yml
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will run a MongoDB server on the db group, this is only for test, please use
+# shared cluster for production env
+
+- name: (re)start mongodb
+  vars:
+    mongodb_image: "{{ mongodb.docker_image | default('mongo:' ~ mongodb.version ) }}"
+  docker_container:
+    name: mongodb
+    image: "{{ mongodb_image }}"
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    hostname: "mongodb"
+    user: "mongodb"
+    volumes:
+      - "{{ db.mongodb.data_volume }}:/data/db"
+    ports:
+      - "27017:27017"
+
+- name: wait until the MongoDB in this host is up and running
+  local_action: wait_for host={{ ansible_host }} port=27017 state=started delay=5 timeout=60
diff --git a/openwhisk/ansible/roles/mongodb/tasks/main.yml b/openwhisk/ansible/roles/mongodb/tasks/main.yml
new file mode 100644
index 0000000..2d27a9c
--- /dev/null
+++ b/openwhisk/ansible/roles/mongodb/tasks/main.yml
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will deploy a database server. Use the role if you want to use CouchCB locally.
+# In deploy mode it will start the MongoDB container.
+# In clean mode it will remove the MongoDB container.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
+
diff --git a/openwhisk/ansible/roles/nginx/files/openwhisk-server-key.pem b/openwhisk/ansible/roles/nginx/files/openwhisk-server-key.pem
new file mode 100644
index 0000000..673da8e
--- /dev/null
+++ b/openwhisk/ansible/roles/nginx/files/openwhisk-server-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA1Xk16caqAdcf3NITVcFsqXHceQf10lPjlKT5Dg+bbJPwS+9p
+qAsYOm0vLP4F72S7/A9IfgMskyUbRsumJlxSzR5Je4obMVQQCBB5fz90RzKdaqIs
+rZdwhmoBfr9VdUqOJjcUPe0TO2FW7QHO0pP1WwIQBfv29n1itkiLlMZOJgNSWcmF
+Y7Hgj/7WDM+wM/XWw9lKCF3J9GsXs9nA70Alf/+bvlbPfSVgK3T4sIcBZl1b+OvK
+s+34zr2xGjW7Zbv1S+MWlG/342bElFoTq+N1KgRLH1rTPdHZfY/qh3hYeBN416xB
+oQ73wV76Jm+U+k1DfVt44Rq8E/RVwLjZiByylQIDAQABAoIBAQCbZwLNbXdDobSr
+Ty8OJaIR9DaY0Set9q3c/v/jsY7myweKb/5Ne84mcmd+bGorrPyAcSvHuE3RzJh/
+wC7zDBCnC95YleBX16dYB447CRl/3yPjha3arT0YTMFL4MO4gA7dWQleT2DJwIHE
+RaWnfFiH6Qd7I8LUC9E5e7RJGNe5KSphJ4bq58WHzEQXc9iqYLzc3AcS3TRENv8d
+u5GAWs7OUOc5mmktlNLptUIxwZBAkW5KHVWMxi8lrMDKqatL2Z/ydCcDezw7IZPd
+7ezQFt80k1I3/Ijxl5knPtn93N9iOk9A9LFmhYnnaBDD2uFFfZGac9A5U6f4Lz5m
+inWBwwo9AoGBAP2ruIB3HSV1ciUcsNPASo4PlAGpndaVGuKsTD1/T/GMR42jVbWP
+isP2HbDt3W9nd3lk3wSFVmtIq+vVHzpW9Q5F/aRBsdU/ofvWGZrlkO9Vo++eUABW
+3MB0gqJGapF+eDLde6IE2IsECckLEB+peLU5K2xSeAkBD/85d1nXCpobAoGBANdv
+AIIR4tmjRdtjimXVNSR53SGy7DEHemLs2Tw40Cg6GWTzA7R0Tv9FJHoSFtXYqZxw
+LP4avV8yEp2q5bfJaFHGxu8LQIGBmDL+yydmuy6vgTVXJD4H7YBH4Xlb0ckaz+ju
+inpzUHaIVvTORy6y8Mu3U39oSU7Yfn2iy1Ic47EPAoGAHH7n0PaQfZ693dFlQ8Q8
+G81AMReetXY2ePQl9FqS3m2FtDF+9VBUpELHfxKZZ2RWFXrxWo6n8JFPTsS4J1OR
+X7MZFRSUJ4JobePVKINVTq1uJwK/teoMDkqISjZizklIs14R/1dQA/3GI6FshEID
+X0g2yopRFaHa7C7Ga38un7UCgYB2wcL28KMrtByLLtkY/6oW3HKw4+/dqzClHck2
+sF7W/ggHpQrSzBbMEzJjdFtQMOp2yUOUI+tmcbTfY1jUslsmUTxSg9JgUa8z1U7p
+/nCK8MZ6P/pDk50xzO4XNy1y/avEzNJbY/vkC45bzuZgcNXahsmpfzSCGUfJPBd2
+wWQmswKBgQCbfjCFq0M1qUhesWDgJnmx0tav1Gn/KkWWe7GYVibj0nuW6QYR8exQ
+ziiMW06DJDD2n2h9mrJ6Rin1dMDRS4nlmzOCqDwRNthtOpr1VDa/U/QRMEr0YiwI
+3OspdVR5BjIw9lbFrM2Idm+fdQtDnj7lHZntN6Env2nNr3DsIAkPZA==
+-----END RSA PRIVATE KEY-----
diff --git a/openwhisk/ansible/roles/nginx/tasks/clean.yml b/openwhisk/ansible/roles/nginx/tasks/clean.yml
new file mode 100644
index 0000000..e888dc5
--- /dev/null
+++ b/openwhisk/ansible/roles/nginx/tasks/clean.yml
@@ -0,0 +1,37 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove nginx containers.
+
+- name: remove nginx
+  docker_container:
+    name: nginx
+    image: nginx
+    state: absent
+  ignore_errors: True
+
+- name: remove nginx config directory
+  file:
+    path: "{{ nginx.confdir }}"
+    state: absent
+  become: "{{ nginx.dir.become }}"
+
+- name: remove nginx log directory
+  file:
+    path: "{{ whisk_logs_dir }}/nginx"
+    state: absent
+  become: "{{ logs.dir.become }}"
diff --git a/openwhisk/ansible/roles/nginx/tasks/deploy.yml b/openwhisk/ansible/roles/nginx/tasks/deploy.yml
new file mode 100644
index 0000000..a41720d
--- /dev/null
+++ b/openwhisk/ansible/roles/nginx/tasks/deploy.yml
@@ -0,0 +1,104 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role starts a nginx component
+
+- name: ensure nginx config directory exists
+  file:
+    path: "{{ nginx.confdir }}"
+    state: directory
+  become: "{{ nginx.dir.become }}"
+
+- name: copy template from local to remote in nginx config directory
+  template:
+    src: nginx.conf.j2
+    dest: "{{ nginx.confdir }}/nginx.conf"
+
+- name: copy cert files from local to remote in nginx config directory
+  copy:
+    src: "{{ nginx.ssl.path }}/{{ item }}"
+    dest: "{{ nginx.confdir }}"
+  with_items:
+        - "{{ nginx.ssl.cert }}"
+        - "{{ nginx.ssl.key }}"
+        - "{{ nginx.ssl.client_ca_cert }}"
+
+- name: copy password files for cert from local to remote in nginx config directory
+  copy:
+    src: "{{ nginx.ssl.path }}/{{ nginx.ssl.password_file }}"
+    dest: "{{ nginx.confdir }}"
+  when: nginx.ssl.password_file
+
+- name: copy controller cert for authentication
+  copy:
+    src: "{{ openwhisk_home }}/ansible/roles/controller/files/{{ item }}"
+    dest: "{{ nginx.confdir }}"
+  with_items:
+        - "{{ controller.ssl.cert }}"
+        - "{{ controller.ssl.key }}"
+  when: controller.protocol == 'https'
+
+- name: ensure nginx log directory is created with permissions
+  file:
+    path: "{{ whisk_logs_dir }}/nginx"
+    state: directory
+    mode: 0777
+  become: "{{ logs.dir.become }}"
+
+- name: "pull the nginx:{{ nginx.version }} image"
+  shell: "docker pull nginx:{{ nginx.version }}"
+  register: result
+  until: (result.rc == 0)
+  retries: "{{ docker.pull.retries }}"
+  delay: "{{ docker.pull.delay }}"
+
+- name: ensure html directory exists
+  stat:
+    path: "{{ nginx.htmldir }}"
+  register: nginx_html_dir_exists
+  when: nginx.htmldir
+
+- name: check if html directory exists
+  fail:
+    msg: html directory does not exist '{{ nginx.htmldir }}'
+  when: nginx.htmldir and not (nginx_html_dir_exists.stat.exists and nginx_html_dir_exists.stat.isdir)
+
+- name: configuration volumes to mount
+  set_fact:
+    volumes:
+      - "{{ whisk_logs_dir }}/nginx:/logs"
+      - "{{ nginx.confdir }}:/etc/nginx"
+
+- name: "optional html volume to mount"
+  set_fact:
+    volumes: "{{ volumes }} + [ '{{ nginx.htmldir }}:/usr/share/nginx/html' ]"
+  when: nginx.htmldir
+
+- name: (re)start nginx
+  docker_container:
+    name: nginx
+    image: nginx:{{ nginx.version }}
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    hostname: "nginx"
+    volumes: "{{ volumes }}"
+    ports:
+      - "{{ nginx.port.http }}:80"
+      - "{{ nginx.port.https }}:443"
+    env:
+      TZ: "{{ docker.timezone }}"
diff --git a/openwhisk/ansible/roles/nginx/tasks/main.yml b/openwhisk/ansible/roles/nginx/tasks/main.yml
new file mode 100644
index 0000000..138b5fb
--- /dev/null
+++ b/openwhisk/ansible/roles/nginx/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install nginx.
+# In deploy mode it will deploy an nginx server.
+# In clean mode it will remove the nginx server as well as nginx.confdir
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/nginx/templates/nginx.conf.j2 b/openwhisk/ansible/roles/nginx/templates/nginx.conf.j2
new file mode 100644
index 0000000..b5ec530
--- /dev/null
+++ b/openwhisk/ansible/roles/nginx/templates/nginx.conf.j2
@@ -0,0 +1,165 @@
+{# this template is used to generate a nginx.conf for booting a nginx server based on the given environment inventory #}
+
+worker_processes {{ nginx.wpn.router }};
+worker_rlimit_nofile 4096;
+
+events {
+{# default: 1024 #}
+    worker_connections 4096;
+}
+
+http {
+{# allow large uploads, need to thread proper limit into here #}
+    client_max_body_size 65M;
+
+    rewrite_log on;
+{# change log format to display the upstream information #}
+    log_format combined-upstream '$remote_addr - $remote_user [$time_local] '
+        '[#tid_$request_id] $request $status $body_bytes_sent '
+        '$http_referer $http_user_agent $upstream_addr';
+    access_log /logs/nginx_access.log combined-upstream;
+
+{# needed to enable keepalive to upstream controllers #}
+    proxy_http_version 1.1;
+    proxy_set_header Connection "";
+    proxy_buffers 16 4k;
+
+{% if controller.protocol == 'https' %}
+    proxy_ssl_session_reuse on;
+    proxy_ssl_name {{ controller.ssl.cn }};
+    proxy_ssl_verify on;
+    proxy_ssl_trusted_certificate /etc/nginx/{{ controller.ssl.cert }};
+    proxy_ssl_protocols TLSv1.1 TLSv1.2;
+    proxy_ssl_certificate /etc/nginx/{{ controller.ssl.cert }};
+    proxy_ssl_certificate_key /etc/nginx/{{ controller.ssl.key }};
+{% endif %}
+
+    gzip_static on;
+    types {
+        text/html                                        html htm shtml;
+        text/css                                         css;
+        text/xml                                         xml;
+        image/gif                                        gif;
+        image/jpeg                                       jpeg jpg;
+        image/png                                        png;
+        image/svg+xml                                    svg svgz;
+        image/x-icon                                     ico;
+        image/x-jng                                      jng;
+        image/x-ms-bmp                                   bmp;
+        application/javascript                           js;
+        application/json                                 json;
+        application/java-archive                         jar war ear;
+        application/pdf                                  pdf;
+    }
+
+    upstream controllers {
+        # Mark the controller as unavailable after fail_timeout seconds, to not get any requests during restart.
+        # Otherwise, nginx would dispatch requests when the container is up, but the backend in the container not.
+        # From the docs:
+        #  "normally, requests with a non-idempotent method (POST, LOCK, PATCH) are not passed to
+        #   the next server if a request has been sent to an upstream server"
+{% for c in groups['controllers'] %}
+        server {{ hostvars[c].ansible_host }}:{{ controller.basePort + groups['controllers'].index(c) }} fail_timeout=60s;
+{% endfor %}
+        keepalive 512;
+    }
+
+{# determine "special users" to send the header accordingly #}
+    map "$remote_user" $special_user {
+      default not_special;
+{% for user in nginx.special_users %}
+      "{{ user }}" special;
+{% endfor %}
+    }
+
+    map "$special_user:$http_x_ow_extra_logging" $extra_logging {
+      default "off";
+      "special:off" off;
+      "special:on" on;
+      "special:" on;
+    }
+
+    proxy_set_header X-OW-EXTRA-LOGGING $extra_logging;
+    # Set the request id generated by nginx as tid-header to the upstream.
+    # This tid is either the request-id generated by ngix or a tid, sent by the caller.
+    proxy_set_header {{ transactions.header }} $request_id;
+
+    # Send the tid always back as header.
+    add_header {{ transactions.header }} $request_id always;
+
+{# Turn off sending information about the server to the client #}
+    server_tokens off;
+
+    # Redirect all http to https.
+    server {
+        listen 80 default_server;
+        server_name _;
+        return 308 https://$host$request_uri;
+    }
+
+    server {
+        listen 443 default ssl;
+
+        # Match namespace, note while OpenWhisk allows a richer character set for a
+        # namespace, not all those characters are permitted in the (sub)domain name.
+        # If namespace does not match, no vanity URL rewriting takes place.
+        server_name ~^(?<namespace>[0-9a-zA-Z-]+)\.{{ whisk_api_localhost_name | default(whisk_api_host_name) | default(whisk_api_localhost_name_default) }}$;
+
+        # Recommended TLS settings from: https://wiki.mozilla.org/Security/Server_Side_TLS
+        ssl_session_cache    shared:SSL:1m;
+        ssl_session_timeout  10m;
+        ssl_certificate      /etc/nginx/{{ nginx.ssl.cert }};
+        ssl_certificate_key  /etc/nginx/{{ nginx.ssl.key }};
+        {% if nginx.ssl.password_file %}
+        ssl_password_file   "/etc/nginx/{{ nginx.ssl.password_file }}";
+        {% endif %}
+        ssl_client_certificate /etc/nginx/{{ nginx.ssl.client_ca_cert }};
+        ssl_verify_client {{ nginx.ssl.verify_client }};
+        ssl_protocols        TLSv1.2;
+        ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
+        ssl_prefer_server_ciphers on;
+        proxy_ssl_session_reuse on;
+
+        # proxy to the web action path
+        location / {
+            if ($namespace) {
+              rewrite    /(.*) /api/v1/web/${namespace}/$1 break;
+            }
+            proxy_pass {{ controller.protocol }}://controllers;
+            proxy_read_timeout 75s; # 70+5 additional seconds to allow controller to terminate request
+        }
+
+        # proxy to 'public/html' web action by convention
+        location = / {
+            if ($namespace) {
+              rewrite    ^ /api/v1/web/${namespace}/public/index.html break;
+            }
+            proxy_pass {{ controller.protocol }}://controllers;
+            proxy_read_timeout 75s; # 70+5 additional seconds to allow controller to terminate request
+        }
+
+        location /blackbox.tar.gz {
+            return 301 https://github.com/apache/openwhisk-runtime-docker/releases/download/sdk%400.1.0/blackbox-0.1.0.tar.gz;
+        }
+
+        location /cli {
+            autoindex on;
+            alias /etc/nginx/cli/go/download;
+        }
+
+        # this is here for backward compatibility
+        location /cli/go/download {
+            rewrite /cli/go/download(.*) /cli$1 permanent;
+        }
+
+        location /metrics {
+            deny all;
+        }
+
+{% if nginx.htmldir %}
+        location /ui {
+            alias /usr/share/nginx/html;
+        }
+{% endif %}
+    }
+}
diff --git a/openwhisk/ansible/roles/prereq/tasks/clean.yml b/openwhisk/ansible/roles/prereq/tasks/clean.yml
new file mode 100644
index 0000000..d8ca6a8
--- /dev/null
+++ b/openwhisk/ansible/roles/prereq/tasks/clean.yml
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove prereq packages
+
+- name: remove requests
+  pip:
+    name: requests
+    state: absent
+  become: true
+
+- name: remove docker
+  pip:
+    name: docker
+    state: absent
+  become: true
+
+- name: remove httplib2
+  pip:
+    name: httplib2
+    state: absent
+  become: true
diff --git a/openwhisk/ansible/roles/prereq/tasks/deploy.yml b/openwhisk/ansible/roles/prereq/tasks/deploy.yml
new file mode 100644
index 0000000..afa8fce
--- /dev/null
+++ b/openwhisk/ansible/roles/prereq/tasks/deploy.yml
@@ -0,0 +1,46 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install all necessary packages for Openwhisk.
+
+- name: check for pip
+  shell: "pip"
+  register: pip_result
+  ignore_errors: true
+
+- name: install pip
+  shell: "curl -k https://bootstrap.pypa.io/get-pip.py | python"
+  become: true
+  when: pip_result.rc != 0
+
+- name: install requests
+  pip:
+    name: requests
+    version: 2.10.0
+  become: true
+
+- name: install docker for python
+  pip:
+    name: docker
+    version: 4.0.2
+  become: true
+
+- name: install httplib2
+  pip:
+    name: httplib2
+    version: 0.9.2
+  become: true
diff --git a/openwhisk/ansible/roles/prereq/tasks/main.yml b/openwhisk/ansible/roles/prereq/tasks/main.yml
new file mode 100644
index 0000000..7a9f4cf
--- /dev/null
+++ b/openwhisk/ansible/roles/prereq/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will prepare target VMs for a whisk deployment using ansible.
+# In deploy mode it will install all necessary packages and programs.
+# In clean mode it will uninstall packages from deploy mode.
+
+- import_tasks: deploy.yml
+  when: mode == "deploy"
+
+- import_tasks: clean.yml
+  when: mode == "clean"
diff --git a/openwhisk/ansible/roles/redis/tasks/clean.yml b/openwhisk/ansible/roles/redis/tasks/clean.yml
new file mode 100644
index 0000000..5a6951d
--- /dev/null
+++ b/openwhisk/ansible/roles/redis/tasks/clean.yml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# Remove redis container.
+
+- name: remove redis
+  docker_container:
+    name: redis
+    state: absent
+  ignore_errors: True
diff --git a/openwhisk/ansible/roles/redis/tasks/deploy.yml b/openwhisk/ansible/roles/redis/tasks/deploy.yml
new file mode 100644
index 0000000..1a8cee0
--- /dev/null
+++ b/openwhisk/ansible/roles/redis/tasks/deploy.yml
@@ -0,0 +1,47 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
+# This role will install redis
+
+- name: "pull the redis:{{ redis.version }} image"
+  shell: "docker pull redis:{{ redis.version }}"
+  register: result
+  until: (result.rc == 0)
+  retries: "{{ docker.pull.retries }}"
+  delay: "{{ docker.pull.delay }}"
+
+- name: (re)start redis
+  docker_container:
+    name: redis
+    image: redis:{{ redis.version }}
+    state: started
+    recreate: true
+    restart_policy: "{{ docker.restart.policy }}"
+    ports:
+      - "{{ redis.port }}:6379"
+    env:
+      TZ: "{{ docker.timezone }}"
+    command:
+      /bin/sh -c
+      "docker-entrypoint.sh --requirepass {{ redis.password }}"
+
+- name: wait until redis is up and running
+  shell: "docker run --link redis:redis --rm redis:{{ redis.version }} redis-cli -h redis -p 6379 -a {{ redis.password }} ping"
+  register: result
+  until: (result.rc == 0) and (result.stdout == 'PONG')
+  retries: 12
+  delay: 5
diff --git a/openwhisk/ansible/roles/redis/tasks/main.yml b/openwhisk/ansible/roles/redis/tasks/main.yml
new file mode 100644
index 0000000..5a4b4d7
--- /dev/null
+++ b/openwhisk/ansible/roles/redis/tasks/main.yml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+---
... 172518 lines suppressed ...