From 29a4f70b2dbda8135defd74729b1ea84ee581b42 Mon Sep 17 00:00:00 2001
From: sabudilovskiy <sabudilovskiy@yandex.ru>
Date: Wed, 22 Mar 2023 19:45:05 +0300
Subject: [PATCH] Initial commit

---
 .clang-format                            |  3 +
 .github/workflows/ci.yml                 | 77 +++++++++++++++++++
 .github/workflows/docker.yaml            | 38 ++++++++++
 .github/workflows/update-submodules.yaml | 22 ++++++
 .gitignore                               | 11 +++
 .gitmodules                              |  3 +
 CMakeLists.txt                           | 55 ++++++++++++++
 Makefile                                 | 96 ++++++++++++++++++++++++
 Makefile.local                           |  1 +
 README.md                                | 46 ++++++++++++
 configs/config_vars.yaml                 |  7 ++
 configs/config_vars_testing.yaml         |  7 ++
 configs/dynamic_config_fallback.json     | 37 +++++++++
 configs/static_config.yaml.in            | 66 ++++++++++++++++
 docker-compose.yml                       | 27 +++++++
 src/hello.cpp                            | 38 ++++++++++
 src/hello.hpp                            | 14 ++++
 src/hello_benchmark.cpp                  | 23 ++++++
 src/hello_test.cpp                       |  8 ++
 src/main.cpp                             | 22 ++++++
 tests/CMakeLists.txt                     | 19 +++++
 tests/__init__.py                        |  0
 tests/conftest.py                        |  1 +
 tests/pytest.ini                         |  3 +
 tests/requirements.txt                   |  1 +
 tests/test_basic.py                      |  5 ++
 third_party/userver                      |  1 +
 27 files changed, 631 insertions(+)
 create mode 100644 .clang-format
 create mode 100644 .github/workflows/ci.yml
 create mode 100644 .github/workflows/docker.yaml
 create mode 100644 .github/workflows/update-submodules.yaml
 create mode 100644 .gitignore
 create mode 100644 .gitmodules
 create mode 100644 CMakeLists.txt
 create mode 100644 Makefile
 create mode 100644 Makefile.local
 create mode 100644 README.md
 create mode 100644 configs/config_vars.yaml
 create mode 100644 configs/config_vars_testing.yaml
 create mode 100644 configs/dynamic_config_fallback.json
 create mode 100644 configs/static_config.yaml.in
 create mode 100644 docker-compose.yml
 create mode 100644 src/hello.cpp
 create mode 100644 src/hello.hpp
 create mode 100644 src/hello_benchmark.cpp
 create mode 100644 src/hello_test.cpp
 create mode 100644 src/main.cpp
 create mode 100644 tests/CMakeLists.txt
 create mode 100644 tests/__init__.py
 create mode 100644 tests/conftest.py
 create mode 100644 tests/pytest.ini
 create mode 100644 tests/requirements.txt
 create mode 100644 tests/test_basic.py
 create mode 160000 third_party/userver

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..276eaf0a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: google
+DerivePointerAlignment: false
+IncludeBlocks: Preserve
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..ff355b9b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,77 @@
+name: CI
+
+'on':
+    pull_request:
+    push:
+        branches:
+          - develop
+
+env:
+    UBSAN_OPTIONS: print_stacktrace=1
+
+jobs:
+    posix:
+        strategy:
+            fail-fast: false
+            matrix:
+                include:
+                  - os: ubuntu-20.04
+                    make: test-debug
+                    info: g++-9 + test-debug
+
+                  - os: ubuntu-20.04
+                    make: test-release
+                    info: g++-9 + test-release
+
+        name: '${{matrix.os}}: ${{matrix.info}}'
+        runs-on: ${{matrix.os}}
+
+        steps:
+          - uses: actions/checkout@v2
+            with:
+                submodules: true
+
+          - name: Reuse ccache directory
+            uses: actions/cache@v2
+            with:
+                path: ~/.ccache
+                key: '${{matrix.os}} ${{matrix.info}} ccache-dir ${{github.ref}} run-${{github.run_number}}'
+                restore-keys: |
+                    ${{matrix.os}} ${{matrix.info}} ccache-dir ${{github.ref}} run-'
+                    ${{matrix.os}} ${{matrix.info}} ccache-
+
+          - name: Install packages
+            run: |
+                sudo apt update
+                sudo apt install --allow-downgrades -y pep8 $(cat third_party/userver/scripts/docs/en/deps/${{matrix.os}}.md | tr '\n' ' ')
+
+          - name: Setup ccache
+            run: |
+                ccache -M 2.0GB
+                ccache -s
+
+          - name: Run ${{matrix.make}}
+            run: |
+                make ${{matrix.make}}
+
+          - name: Test install ${{matrix.make}}
+            if: matrix.make == 'test-release'
+            run: |
+                make dist-clean
+                make install PREFIX=`pwd`/local_installation/
+
+          - name: Test run after install
+            if: matrix.make == 'test-release'
+            run: |
+                ./local_installation/bin/service_template --config=./local_installation/etc/service_template/static_config.yaml &
+
+          - name: Check work run service
+            if: matrix.make == 'test-release'
+            run: |
+                ps aux | grep service_template | grep config && curl http://localhost:8080/ping -v
+
+          - name: Stop all
+            if: matrix.make == 'test-release'
+            run: |
+                killall service_template
+
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
new file mode 100644
index 00000000..9c4feea3
--- /dev/null
+++ b/.github/workflows/docker.yaml
@@ -0,0 +1,38 @@
+name: Docker build
+
+'on':
+    pull_request:
+    push:
+        branches:
+          - master
+          - develop
+          - feature/**
+
+jobs:
+    tests:
+        runs-on: ubuntu-latest
+        steps:
+          - uses: actions/checkout@v2
+            with:
+                submodules: true
+
+          - name: Reuse ccache directory
+            uses: actions/cache@v2
+            with:
+                path: .ccache
+                key: 'ccache-dir-${{github.ref}}_run-${{github.run_number}}'
+                restore-keys: |
+                    ccache-dir-${{github.ref}}_run-
+                    ccache-
+
+          - name: Setup ccache
+            run: docker-compose run --rm service_template-container bash -c 'ccache -M 2.0GB && ccache -s'
+
+          - name: Cmake
+            run: make docker-cmake-release
+
+          - name: Build
+            run: make docker-build-release
+
+          - name: Run tests
+            run: make docker-test-release
diff --git a/.github/workflows/update-submodules.yaml b/.github/workflows/update-submodules.yaml
new file mode 100644
index 00000000..9603da83
--- /dev/null
+++ b/.github/workflows/update-submodules.yaml
@@ -0,0 +1,22 @@
+name: Update submodules
+
+on:
+  workflow_dispatch:
+
+jobs:
+  update:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Update submodules
+        run: |
+          git submodule update --init
+
+      - name: Commit & push changes
+        run: |
+          git config user.email "actions@github.com"
+          git config user.name "GitHub Actions - update submodules"
+          git commit -am "Update submodules"
+          git push
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..855e7587
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+__pycache__
+build*
+compile_commands.json
+.cache/
+.idea/
+.vscode/
+.cores/
+.ccache/
+cmake-build-*
+Testing/
+configs/static_config.yaml
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..4d25646e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "third_party/userver"]
+	path = third_party/userver
+	url = https://github.com/userver-framework/userver.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..7dad5d34
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,55 @@
+cmake_minimum_required(VERSION 3.12)
+project(service_template CXX)
+
+include(third_party/userver/cmake/SetupEnvironment.cmake)
+include(GNUInstallDirs)
+
+add_subdirectory(third_party/userver)
+
+
+# Common sources
+add_library(${PROJECT_NAME}_objs OBJECT
+    src/hello.hpp
+    src/hello.cpp
+)
+target_link_libraries(${PROJECT_NAME}_objs PUBLIC userver-core)
+
+
+# The Service
+add_executable(${PROJECT_NAME} src/main.cpp)
+target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_objs)
+
+
+# Unit Tests
+add_executable(${PROJECT_NAME}_unittest
+    src/hello_test.cpp
+)
+target_link_libraries(${PROJECT_NAME}_unittest PRIVATE ${PROJECT_NAME}_objs userver-utest)
+add_google_tests(${PROJECT_NAME}_unittest)
+
+
+# Benchmarks
+add_executable(${PROJECT_NAME}_benchmark
+	src/hello_benchmark.cpp
+)
+target_link_libraries(${PROJECT_NAME}_benchmark PRIVATE ${PROJECT_NAME}_objs userver-ubench)
+add_google_benchmark_tests(${PROJECT_NAME}_benchmark)
+
+# Functional Tests
+add_subdirectory(tests)
+
+if(DEFINED ENV{PREFIX})
+	message(STATUS "Set install prefix: $ENV{PREFIX}")
+	file(TO_CMAKE_PATH "$ENV{PREFIX}" PREFIX_PATH)
+	set(CMAKE_INSTALL_PREFIX ${PREFIX_PATH})
+endif()
+
+set(CONFIG_VAR_PATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME}/config_vars.yaml)
+set(CONFIG_FALLBACK_PATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME}/dynamic_config_fallback.json)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configs/static_config.yaml.in ${CMAKE_CURRENT_SOURCE_DIR}/configs/static_config.yaml)
+
+FILE(GLOB CONFIGS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/configs/*.yaml ${CMAKE_CURRENT_SOURCE_DIR}/configs/*.json)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${PROJECT_NAME})
+install(FILES ${CONFIGS_FILES} DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME} COMPONENT ${PROJECT_NAME})
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..e6a96841
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,96 @@
+CMAKE_COMMON_FLAGS ?= -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
+CMAKE_DEBUG_FLAGS ?= -DUSERVER_SANITIZE='addr ub'
+CMAKE_RELEASE_FLAGS ?=
+CMAKE_OS_FLAGS ?= -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 -DUSERVER_FEATURE_REDIS_HI_MALLOC=1
+NPROCS ?= $(shell nproc)
+CLANG_FORMAT ?= clang-format
+
+# NOTE: use Makefile.local for customization
+-include Makefile.local
+
+.PHONY: all
+all: test-debug test-release
+
+# Debug cmake configuration
+build_debug/Makefile:
+	@git submodule update --init
+	@mkdir -p build_debug
+	@cd build_debug && \
+      cmake -DCMAKE_BUILD_TYPE=Debug $(CMAKE_COMMON_FLAGS) $(CMAKE_DEBUG_FLAGS) $(CMAKE_OS_FLAGS) $(CMAKE_OPTIONS) ..
+
+# Release cmake configuration
+build_release/Makefile:
+	@git submodule update --init
+	@mkdir -p build_release
+	@cd build_release && \
+      cmake -DCMAKE_BUILD_TYPE=Release $(CMAKE_COMMON_FLAGS) $(CMAKE_RELEASE_FLAGS) $(CMAKE_OS_FLAGS) $(CMAKE_OPTIONS) ..
+
+# Run cmake
+.PHONY: cmake-debug cmake-release
+cmake-debug cmake-release: cmake-%: build_%/Makefile
+
+# Build using cmake
+.PHONY: build-debug build-release
+build-debug build-release: build-%: cmake-%
+	@cmake --build build_$* -j $(NPROCS) --target service_template
+
+# Test
+.PHONY: test-debug test-release
+test-debug test-release: test-%: build-%
+	@cmake --build build_$* -j $(NPROCS) --target service_template_unittest
+	@cmake --build build_$* -j $(NPROCS) --target service_template_benchmark
+	@cd build_$* && ((test -t 1 && GTEST_COLOR=1 PYTEST_ADDOPTS="--color=yes" ctest -V) || ctest -V)
+	@pep8 tests
+
+# Start the service (via testsuite service runner)
+.PHONY: service-start-debug service-start-release
+service-start-debug service-start-release: service-start-%: build-%
+	@cd ./build_$* && $(MAKE) start-service_template
+
+# Cleanup data
+.PHONY: clean-debug clean-release
+clean-debug clean-release: clean-%:
+	cd build_$* && $(MAKE) clean
+
+.PHONY: dist-clean
+dist-clean:
+	@rm -rf build_*
+	@rm -f ./configs/static_config.yaml
+	@rm -rf tests/__pycache__/
+	@rm -rf tests/.pytest_cache/
+
+# Install
+.PHONY: install-debug install-release
+install-debug install-release: install-%: build-%
+	@cd build_$* && \
+		cmake --install . -v --component service_template
+
+.PHONY: install
+install: install-release
+
+# Format the sources
+.PHONY: format
+format:
+	@find src -name '*pp' -type f | xargs $(CLANG_FORMAT) -i
+	@find tests -name '*.py' -type f | xargs autopep8 -i
+
+# Internal hidden targets that are used only in docker environment
+.PHONY: --in-docker-start-debug --in-docker-start-release
+--in-docker-start-debug --in-docker-start-release: --in-docker-start-%: install-%
+	@/home/user/.local/bin/service_template \
+		--config /home/user/.local/etc/service_template/static_config.yaml
+
+# Build and run service in docker environment
+.PHONY: docker-start-service-debug docker-start-service-release
+docker-start-service-debug docker-start-service-release: docker-start-service-%:
+	@docker-compose run -p 8080:8080 --rm service_template-container $(MAKE) -- --in-docker-start-$*
+
+# Start specific target in docker environment
+.PHONY: docker-cmake-debug docker-build-debug docker-test-debug docker-clean-debug docker-install-debug docker-cmake-release docker-build-release docker-test-release docker-clean-release docker-install-release
+docker-cmake-debug docker-build-debug docker-test-debug docker-clean-debug docker-install-debug docker-cmake-release docker-build-release docker-test-release docker-clean-release docker-install-release: docker-%:
+	docker-compose run --rm service_template-container $(MAKE) $*
+
+# Stop docker container and cleanup data
+.PHONY: docker-clean-data
+docker-clean-data:
+	@docker-compose down -v
diff --git a/Makefile.local b/Makefile.local
new file mode 100644
index 00000000..5e40df6f
--- /dev/null
+++ b/Makefile.local
@@ -0,0 +1 @@
+CMAKE_COMMON_FLAGS += -DUSERVER_FEATURE_CRYPTOPP_BLAKE=0 -DUSERVER_FEATURE_GRPC_CHANNELZ=0
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..f2e174da
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# service_template
+
+Template of a C++ service that uses [userver framework](https://github.com/userver-framework/userver).
+
+
+## Download and Build
+
+To create your own userver-based service follow the following steps:
+
+1. Press the green "Use this template button" at the top of this github page
+2. Clone the service `git clone your-service-repo && cd your-service-repo && git submodule update --init`
+3. Give a proper name to your service and replace all the occurences of "service_template" string with that name
+4. Feel free to tweak, adjust or fully rewrite the source code of your service.
+
+
+## Makefile
+
+Makefile contains typicaly useful targets for development:
+
+* `make build-debug` - debug build of the service with all the assertions and sanitizers enabled
+* `make build-release` - release build of the service with LTO
+* `make test-debug` - does a `make build-debug` and runs all the tests on the result
+* `make test-release` - does a `make build-release` and runs all the tests on the result
+* `make service-start-debug` - builds the service in debug mode and starts it
+* `make service-start-release` - builds the service in release mode and starts it
+* `make` or `make all` - builds and runs all the tests in release and debug modes
+* `make format` - autoformat all the C++ and Python sources
+* `make clean-` - cleans the object files
+* `make dist-clean` - clean all, including the CMake cached configurations
+* `make install` - does a `make build-release` and run install in directory set in environment `PREFIX`
+* `make install-debug` - does a `make build-debug` and runs install in directory set in environment `PREFIX`
+* `make docker-COMMAND` - run `make COMMAND` in docker environment
+* `make docker-build-debug` - debug build of the service with all the assertions and sanitizers enabled in docker environment
+* `make docker-test-debug` - does a `make build-debug` and runs all the tests on the result in docker environment
+* `make docker-start-service-release` - does a `make install-release` and runs service in docker environment
+* `make docker-start-service-debug` - does a `make install-debug` and runs service in docker environment
+* `make docker-clean-data` - stop docker containers
+
+Edit `Makefile.local` to change the default configuration and build options.
+
+
+## License
+
+The original template is distributed under the [Apache-2.0 License](https://github.com/userver-framework/userver/blob/develop/LICENSE)
+and [CLA](https://github.com/userver-framework/userver/blob/develop/CONTRIBUTING.md). Services based on the template may change
+the license and CLA.
diff --git a/configs/config_vars.yaml b/configs/config_vars.yaml
new file mode 100644
index 00000000..a7ec8b1a
--- /dev/null
+++ b/configs/config_vars.yaml
@@ -0,0 +1,7 @@
+worker-threads: 4
+worker-fs-threads: 2
+logger-level: debug
+
+is_testing: false
+
+server-port: 8080
diff --git a/configs/config_vars_testing.yaml b/configs/config_vars_testing.yaml
new file mode 100644
index 00000000..828e6586
--- /dev/null
+++ b/configs/config_vars_testing.yaml
@@ -0,0 +1,7 @@
+worker-threads: 4
+worker-fs-threads: 2
+logger-level: debug
+
+is_testing: true
+
+server-port: 8080
diff --git a/configs/dynamic_config_fallback.json b/configs/dynamic_config_fallback.json
new file mode 100644
index 00000000..503ad118
--- /dev/null
+++ b/configs/dynamic_config_fallback.json
@@ -0,0 +1,37 @@
+{
+  "USERVER_CACHES": {},
+  "USERVER_CANCEL_HANDLE_REQUEST_BY_DEADLINE": false,
+  "USERVER_CHECK_AUTH_IN_HANDLERS": false,
+  "USERVER_DUMPS": {},
+  "USERVER_HTTP_PROXY": "",
+  "USERVER_HANDLER_STREAM_API_ENABLED": false,
+  "USERVER_LOG_REQUEST": true,
+  "USERVER_LOG_REQUEST_HEADERS": false,
+  "USERVER_LRU_CACHES": {},
+  "USERVER_RPS_CCONTROL_CUSTOM_STATUS": {},
+  "USERVER_TASK_PROCESSOR_PROFILER_DEBUG": {},
+  "HTTP_CLIENT_CONNECTION_POOL_SIZE": 1000,
+  "HTTP_CLIENT_CONNECT_THROTTLE": {
+    "http-limit": 6000,
+    "http-per-second": 1500,
+    "https-limit": 100,
+    "https-per-second": 25,
+    "per-host-limit": 3000,
+    "per-host-per-second": 500
+  },
+  "HTTP_CLIENT_ENFORCE_TASK_DEADLINE": {
+    "cancel-request": false,
+    "update-timeout": false
+  },
+  "USERVER_TASK_PROCESSOR_QOS": {
+    "default-service": {
+      "default-task-processor": {
+        "wait_queue_overload": {
+          "action": "ignore",
+          "length_limit": 5000,
+          "time_limit_us": 3000
+        }
+      }
+    }
+  }
+}
diff --git a/configs/static_config.yaml.in b/configs/static_config.yaml.in
new file mode 100644
index 00000000..83e51975
--- /dev/null
+++ b/configs/static_config.yaml.in
@@ -0,0 +1,66 @@
+# yaml
+
+config_vars: @CONFIG_VAR_PATH@
+
+components_manager:
+    coro_pool:
+        initial_size: 500             # Preallocate 500 coroutines at startup.
+        max_size: 1000                # Do not keep more than 1000 preallocated coroutines.
+
+    task_processors:                  # Task processor is an executor for coroutine tasks
+
+        main-task-processor:          # Make a task processor for CPU-bound couroutine tasks.
+            worker_threads: $worker-threads         # Process tasks in 4 threads.
+            thread_name: main-worker  # OS will show the threads of this task processor with 'main-worker' prefix.
+
+        fs-task-processor:            # Make a separate task processor for filesystem bound tasks.
+            thread_name: fs-worker
+            worker_threads: $worker-fs-threads
+
+    default_task_processor: main-task-processor
+
+    components:                       # Configuring components that were registered via component_list
+        server:
+            listener:                 # configuring the main listening socket...
+                port: $server-port            # ...to listen on this port and...
+                task_processor: main-task-processor    # ...process incoming requests on this task processor.
+        logging:
+            fs-task-processor: fs-task-processor
+            loggers:
+                default:
+                    file_path: '@stderr'
+                    level: $logger-level
+                    overflow_behavior: discard  # Drop logs if the system is too busy to write them down.
+
+        tracer:                              # Component that helps to trace execution times and requests in logs.
+            service-name: service_template   # "You know. You all know exactly who I am. Say my name. " (c)
+
+        dynamic-config:                      # Dynamic config storage options, do nothing
+            fs-cache-path: ''
+        dynamic-config-fallbacks:            # Load options from file and push them into the dynamic config storage.
+            fallback-path: @CONFIG_FALLBACK_PATH@
+        testsuite-support: {}
+
+        http-client:
+            load-enabled: $is_testing
+            fs-task-processor: fs-task-processor
+
+        dns-client:
+            fs-task-processor: fs-task-processor
+
+        tests-control:
+            load-enabled: $is_testing
+            path: /tests/{action}
+            method: POST
+            task_processor: main-task-processor
+        handler-ping:
+            path: /ping
+            method: GET
+            task_processor: main-task-processor
+            throttling_enabled: false
+            url_trailing_slash: strict-match
+
+        handler-hello:                    # Finally! Our handler.
+            path: /hello                  # Registering handler by URL '/hello'.
+            method: GET,POST              # It will only reply to GET (HEAD) and POST requests.
+            task_processor: main-task-processor  # Run it on CPU bound task processor
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..90d56f04
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,27 @@
+version: "2.3"
+
+services:
+    service_template-container:
+        image: ghcr.io/userver-framework/docker-userver-build-base:v1a
+        privileged: true
+        network_mode: bridge
+        environment:
+          - PREFIX=${PREFIX:-~/.local}
+          - CC
+          - CCACHE_DIR=/service_template/.ccache
+          - CCACHE_HASHDIR
+          - CCACHE_NOHASHDIR
+          - CCACHE_PREFIX
+          - CCACHE_SIZE
+          - CMAKE_OPTS
+          - CORES_DIR=/cores
+          - CXX
+          - MAKE_OPTS
+        volumes:
+          - .:/service_template:rw
+          - ./third_party/userver/tools/docker:/tools:ro
+        ports:
+          - 8080:8080
+        working_dir: /service_template
+        entrypoint:
+          - /tools/run_as_user.sh
diff --git a/src/hello.cpp b/src/hello.cpp
new file mode 100644
index 00000000..d61e628f
--- /dev/null
+++ b/src/hello.cpp
@@ -0,0 +1,38 @@
+#include "hello.hpp"
+
+#include <fmt/format.h>
+
+#include <userver/server/handlers/http_handler_base.hpp>
+
+namespace service_template {
+
+namespace {
+
+class Hello final : public userver::server::handlers::HttpHandlerBase {
+public:
+  static constexpr std::string_view kName = "handler-hello";
+
+  using HttpHandlerBase::HttpHandlerBase;
+
+  std::string HandleRequestThrow(
+      const userver::server::http::HttpRequest &request,
+      userver::server::request::RequestContext &) const override {
+    return service_template::SayHelloTo(request.GetArg("name"));
+  }
+};
+
+} // namespace
+
+std::string SayHelloTo(std::string_view name) {
+  if (name.empty()) {
+    name = "unknown user";
+  }
+
+  return fmt::format("Hello, {}!\n", name);
+}
+
+void AppendHello(userver::components::ComponentList &component_list) {
+  component_list.Append<Hello>();
+}
+
+} // namespace service_template
diff --git a/src/hello.hpp b/src/hello.hpp
new file mode 100644
index 00000000..a438f8b0
--- /dev/null
+++ b/src/hello.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include <userver/components/component_list.hpp>
+
+namespace service_template {
+
+std::string SayHelloTo(std::string_view name);
+
+void AppendHello(userver::components::ComponentList &component_list);
+
+} // namespace service_template
diff --git a/src/hello_benchmark.cpp b/src/hello_benchmark.cpp
new file mode 100644
index 00000000..528d5778
--- /dev/null
+++ b/src/hello_benchmark.cpp
@@ -0,0 +1,23 @@
+#include "hello.hpp"
+
+#include <cstdint>   // for std::uint64_t
+#include <iterator>  // for std::size
+#include <string_view>
+
+#include <benchmark/benchmark.h>
+#include <userver/engine/run_standalone.hpp>
+
+void HelloBenchmark(benchmark::State& state) {
+  userver::engine::RunStandalone([&] {
+    constexpr std::string_view kNames[] = {"userver", "is", "awesome", "!"};
+    std::uint64_t i = 0;
+
+    for (auto _ : state) {
+      const auto name = kNames[i++ % std::size(kNames)];
+      auto result = service_template::SayHelloTo(name);
+      benchmark::DoNotOptimize(result);
+    }
+  });
+}
+
+BENCHMARK(HelloBenchmark);
diff --git a/src/hello_test.cpp b/src/hello_test.cpp
new file mode 100644
index 00000000..68186dfd
--- /dev/null
+++ b/src/hello_test.cpp
@@ -0,0 +1,8 @@
+#include "hello.hpp"
+
+#include <userver/utest/utest.hpp>
+
+UTEST(SayHelloTo, Basic) {
+  EXPECT_EQ(service_template::SayHelloTo("Developer"), "Hello, Developer!\n");
+  EXPECT_EQ(service_template::SayHelloTo({}), "Hello, unknown user!\n");
+}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 00000000..b8b41661
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,22 @@
+#include <userver/components/minimal_server_component_list.hpp>
+#include <userver/clients/dns/component.hpp>
+#include <userver/clients/http/component.hpp>
+#include <userver/server/handlers/ping.hpp>
+#include <userver/server/handlers/tests_control.hpp>
+#include <userver/testsuite/testsuite_support.hpp>
+#include <userver/utils/daemon_run.hpp>
+
+#include "hello.hpp"
+
+int main(int argc, char* argv[]) {
+  auto component_list = userver::components::MinimalServerComponentList()
+                            .Append<userver::server::handlers::Ping>()
+                            .Append<userver::components::TestsuiteSupport>()
+                            .Append<userver::components::HttpClient>()
+                            .Append<userver::clients::dns::Component>()
+                            .Append<userver::server::handlers::TestsControl>();
+
+  service_template::AppendHello(component_list);
+
+  return userver::utils::DaemonMain(argc, argv, component_list);
+}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 00000000..ddefe49c
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,19 @@
+include(UserverTestsuite)
+
+set(CONFIG_VARS_PATH "${CMAKE_SOURCE_DIR}/configs/config_vars_testing.yaml")
+if (EXISTS "${CONFIG_VARS_PATH}")
+    set(PYTEST_ARGS_CONFIG_VARS "--service-config-vars=${CONFIG_VARS_PATH}")
+else()
+    set(PYTEST_ARGS_CONFIG_VARS "")
+endif()
+
+userver_testsuite_add(
+  SERVICE_TARGET service_template
+  REQUIREMENTS ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt
+  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+  PYTEST_ARGS
+  --service-config=${CMAKE_SOURCE_DIR}/configs/static_config.yaml
+  --service-binary=${CMAKE_BINARY_DIR}/service_template
+  --config-fallback=${CMAKE_SOURCE_DIR}/configs/dynamic_config_fallback.json
+  ${PYTEST_ARGS_CONFIG_VARS}
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..699ab947
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1 @@
+pytest_plugins = ['pytest_userver.plugins.core']
diff --git a/tests/pytest.ini b/tests/pytest.ini
new file mode 100644
index 00000000..c64b29ba
--- /dev/null
+++ b/tests/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+asyncio_mode = auto
+log_level = debug
diff --git a/tests/requirements.txt b/tests/requirements.txt
new file mode 100644
index 00000000..9a64ad49
--- /dev/null
+++ b/tests/requirements.txt
@@ -0,0 +1 @@
+yandex-taxi-testsuite >= 0.1.7.2
diff --git a/tests/test_basic.py b/tests/test_basic.py
new file mode 100644
index 00000000..452e7372
--- /dev/null
+++ b/tests/test_basic.py
@@ -0,0 +1,5 @@
+# Start via `make test-debug` or `make test-release`
+async def test_basic(service_client):
+    response = await service_client.post('/hello', params={'name': 'Tester'})
+    assert response.status == 200
+    assert response.text == 'Hello, Tester!\n'
diff --git a/third_party/userver b/third_party/userver
new file mode 160000
index 00000000..ed2a39b5
--- /dev/null
+++ b/third_party/userver
@@ -0,0 +1 @@
+Subproject commit ed2a39b5a0307cf444c2e3353c71398a6c2523a8