├── .github ├── dependabot.yaml └── workflows │ ├── README.md │ └── ci.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cloudbuild.yaml ├── docs ├── development-principles.md └── img │ ├── architecture-diagram.png │ ├── online-boutique-frontend-1.png │ └── online-boutique-frontend-2.png ├── hack ├── README.md ├── license_header.txt ├── make-docker-images.sh ├── make-release-artifacts.sh └── make-release.sh ├── istio-manifests ├── frontend-gateway.yaml ├── frontend.yaml └── whitelist-egress-googleapis.yaml ├── kubernetes-manifests ├── README.md ├── adservice.yaml ├── cartservice.yaml ├── checkoutservice.yaml ├── currencyservice.yaml ├── emailservice.yaml ├── frontend.yaml ├── loadgenerator.yaml ├── paymentservice.yaml ├── productcatalogservice.yaml ├── recommendationservice.yaml ├── redis.yaml └── shippingservice.yaml ├── pb ├── demo.proto └── grpc │ └── health │ └── v1 │ └── health.proto ├── skaffold.yaml └── src ├── .gitignore ├── adservice ├── .gitignore ├── Dockerfile ├── README.md ├── build.gradle ├── genproto.sh ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── hipstershop │ │ ├── AdService.java │ │ └── AdServiceClient.java │ ├── proto │ └── demo.proto │ └── resources │ └── log4j2.xml ├── cartservice ├── .dockerignore ├── .gitignore ├── ActivitySourceUtil.cs ├── CartServiceImpl.cs ├── Dockerfile ├── HealthImpl.cs ├── Program.cs ├── README.md ├── cartservice.csproj ├── cartstore │ ├── Bogo.cs │ ├── ConfigHelper.cs │ ├── DatabaseCache.cs │ ├── LocalCartStore.cs │ ├── RedisCartStore.cs │ └── UserId.cs ├── genproto.bat ├── genproto.sh ├── grpc_generated │ ├── Demo.cs │ └── DemoGrpc.cs ├── interfaces │ └── ICartStore.cs ├── start └── tests │ ├── .gitignore │ ├── CartServiceTests.cs │ ├── cartservice.tests.csproj │ ├── cartservice.tests.csproj.user │ ├── docker-compose.yaml │ └── otel-config.yaml ├── checkoutservice ├── Dockerfile ├── README.md ├── examples │ └── placeorder.js ├── genproto.sh ├── genproto │ ├── demo.pb.go │ └── demo_grpc.pb.go ├── go.mod ├── go.sum ├── main.go └── money │ ├── money.go │ └── money_test.go ├── currencyservice ├── .dockerignore ├── .gitignore ├── Dockerfile ├── data │ └── currency_conversion.json ├── genproto.sh ├── package-lock.json ├── package.json ├── proto │ ├── demo.proto │ └── grpc │ │ └── health │ │ └── v1 │ │ └── health.proto ├── server.js └── tracing.js ├── emailservice ├── Dockerfile ├── demo_pb2.py ├── demo_pb2_grpc.py ├── email_client.py ├── email_server.py ├── genproto.sh ├── logger.py ├── requirements.in ├── requirements.txt └── templates │ └── confirmation.html ├── frontend ├── .gitkeep ├── Dockerfile ├── README.md ├── genproto.sh ├── genproto │ ├── demo.pb.go │ └── demo_grpc.pb.go ├── go.mod ├── go.sum ├── handlers.go ├── main.go ├── middleware.go ├── money │ ├── money.go │ └── money_test.go ├── rpc.go ├── static │ ├── favicon.ico │ ├── icons │ │ ├── Hipster_Advert2.svg │ │ ├── Hipster_CartIcon.svg │ │ ├── Hipster_CheckOutIcon.svg │ │ ├── Hipster_CurrencyIcon.svg │ │ ├── Hipster_DownArrow.svg │ │ ├── Hipster_FacebookIcon.svg │ │ ├── Hipster_GooglePlayIcon.svg │ │ ├── Hipster_HelpIcon.svg │ │ ├── Hipster_HeroLogo.svg │ │ ├── Hipster_HeroLogoCyan.svg │ │ ├── Hipster_HotProducts.svg │ │ ├── Hipster_InstagramIcon.svg │ │ ├── Hipster_KitchenwareOffer.svg │ │ ├── Hipster_NavLogo.svg │ │ ├── Hipster_OtherProducts.svg │ │ ├── Hipster_PinterestIcon.svg │ │ ├── Hipster_ProfileIcon.svg │ │ ├── Hipster_SearchIcon.svg │ │ ├── Hipster_TwitterIcon.svg │ │ ├── Hipster_UpDownControl.svg │ │ └── Hipster_YoutubeIcon.svg │ ├── images │ │ ├── Advert2BannerImage.png │ │ ├── AdvertBannerImage.png │ │ ├── HeroBannerImage.png │ │ ├── HeroBannerImage2.png │ │ └── VRHeadsets.png │ ├── img │ │ └── products │ │ │ ├── air-plant.jpg │ │ │ ├── barista-kit.jpg │ │ │ ├── camera-lens.jpg │ │ │ ├── camp-mug.jpg │ │ │ ├── city-bike.jpg │ │ │ ├── credits.txt │ │ │ ├── film-camera.jpg │ │ │ ├── record-player.jpg │ │ │ ├── terrarium.jpg │ │ │ └── typewriter.jpg │ └── styles │ │ ├── cart.css │ │ ├── order.css │ │ └── styles.css └── templates │ ├── ad.html │ ├── cart.html │ ├── error.html │ ├── footer.html │ ├── header.html │ ├── home.html │ ├── order.html │ ├── product.html │ └── recommendations.html ├── loadgenerator ├── Dockerfile ├── loadgen.sh ├── locustfile.py ├── requirements.in └── requirements.txt ├── paymentservice ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── charge.js ├── examples │ └── charge.js ├── genproto.sh ├── index.js ├── package-lock.json ├── package.json ├── proto │ ├── demo.proto │ └── grpc │ │ └── health │ │ └── v1 │ │ └── health.proto ├── server.js └── tracing.js ├── productcatalogservice ├── Dockerfile ├── README.md ├── genproto.sh ├── genproto │ ├── demo.pb.go │ └── demo_grpc.pb.go ├── go.mod ├── go.sum ├── products.json ├── server.go └── server_test.go ├── recommendationservice ├── .gitignore ├── Dockerfile ├── client.py ├── demo_pb2.py ├── demo_pb2_grpc.py ├── fixed_propagator.py ├── genproto.sh ├── logger.py ├── recommendation_server.py ├── requirements.in └── requirements.txt └── shippingservice ├── Dockerfile ├── README.md ├── genproto.sh ├── genproto ├── demo.pb.go └── demo_grpc.pb.go ├── go.mod ├── go.sum ├── main.go ├── quote.go ├── shippingservice_test.go └── tracker.go /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/src/adservice" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "docker" 8 | directory: "/src/adservice" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: "nuget" 12 | directory: "/src/cartservice" 13 | schedule: 14 | interval: "daily" 15 | - package-ecosystem: "docker" 16 | directory: "/src/cartservice" 17 | schedule: 18 | interval: "daily" 19 | - package-ecosystem: "gomod" 20 | directory: "/src/checkoutservice" 21 | schedule: 22 | interval: "daily" 23 | - package-ecosystem: "docker" 24 | directory: "/src/checkoutservice" 25 | schedule: 26 | interval: "daily" 27 | - package-ecosystem: "npm" 28 | directory: "/src/currencyservice" 29 | schedule: 30 | interval: "daily" 31 | - package-ecosystem: "docker" 32 | directory: "/src/currencyservice" 33 | schedule: 34 | interval: "daily" 35 | - package-ecosystem: "pip" 36 | directory: "/src/emailservice" 37 | schedule: 38 | interval: "daily" 39 | - package-ecosystem: "docker" 40 | directory: "/src/emailservice" 41 | schedule: 42 | interval: "daily" 43 | - package-ecosystem: "gomod" 44 | directory: "/src/frontend" 45 | schedule: 46 | interval: "daily" 47 | - package-ecosystem: "docker" 48 | directory: "/src/frontend" 49 | schedule: 50 | interval: "daily" 51 | - package-ecosystem: "pip" 52 | directory: "/src/loadgenerator" 53 | schedule: 54 | interval: "daily" 55 | - package-ecosystem: "docker" 56 | directory: "/src/loadgenerator" 57 | schedule: 58 | interval: "daily" 59 | - package-ecosystem: "npm" 60 | directory: "/src/paymentservice" 61 | schedule: 62 | interval: "daily" 63 | - package-ecosystem: "docker" 64 | directory: "/src/paymentservice" 65 | schedule: 66 | interval: "daily" 67 | - package-ecosystem: "gomod" 68 | directory: "/src/productcatalogservice" 69 | schedule: 70 | interval: "daily" 71 | - package-ecosystem: "docker" 72 | directory: "/src/productcatalogservice" 73 | schedule: 74 | interval: "daily" 75 | - package-ecosystem: "pip" 76 | directory: "/src/recommendationservice" 77 | schedule: 78 | interval: "daily" 79 | - package-ecosystem: "docker" 80 | directory: "/src/recommendationservice" 81 | schedule: 82 | interval: "daily" 83 | - package-ecosystem: "gomod" 84 | directory: "/src/shippingservice" 85 | schedule: 86 | interval: "daily" 87 | - package-ecosystem: "docker" 88 | directory: "/src/shippingservice" 89 | schedule: 90 | interval: "daily" 91 | - package-ecosystem: "github-actions" 92 | directory: "/" 93 | schedule: 94 | interval: "daily" 95 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflows 2 | 3 | ## Setup 4 | - workloads run using [GitHub self-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) 5 | - project admins maintain a private Google Compute Engine VM for running tests 6 | - VM should be at least n1-standard-4 with 50GB persistent disk 7 | - instructions for setting up the VM can be found in repo settings under "Actions" 8 | - ⚠️ WARNING: VM should be set up with no GCP service account 9 | - external contributors could contribute malicious PRs to run code on our test VM. Ensure no service accounts or other secrets exist on the VM 10 | - An empty GCP project should be used for extra security 11 | - to set up dependencies, run the following commands: 12 | ``` 13 | # install kubectl 14 | sudo apt-get install kubectl 15 | 16 | # install kind 17 | curl -Lo ./kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(uname)-amd64" && \ 18 | chmod +x ./kind && \ 19 | sudo mv ./kind /usr/local/bin 20 | 21 | # install skaffold 22 | curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \ 23 | chmod +x skaffold && \ 24 | sudo mv skaffold /usr/local/bin 25 | 26 | # install docker 27 | sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common && \ 28 | curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - && \ 29 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \ 30 | sudo apt update && \ 31 | sudo apt install docker-ce && \ 32 | sudo usermod -aG docker ${USER} 33 | 34 | # logout and back on 35 | exit 36 | ``` 37 | - ensure GitHub Actions runs as background service: 38 | ``` 39 | sudo ∼/actions-runner/svc.sh install 40 | sudo ∼/actions-runner/svc.sh start 41 | ``` 42 | 43 | 44 | --- 45 | ## Workflows 46 | 47 | ### ci.yaml 48 | 49 | #### Triggers 50 | - commits pushed to master 51 | - PRs to master 52 | - PRs to release/ branches 53 | 54 | #### Actions 55 | - ensures kind cluster is running 56 | - builds all containers in src/ 57 | - deploys local containers to kind 58 | - ensures all pods reach ready state 59 | - ensures HTTP request to frontend returns HTTP status 200 60 | - deploys manifests from /releases 61 | - ensures all pods reach ready state 62 | - ensures HTTP request to frontend returns HTTP status 200 63 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "Continuous Integration" 2 | on: 3 | push: 4 | # run on pushes to main 5 | branches: 6 | - main 7 | pull_request: 8 | # run on pull requests targeting main 9 | branches: 10 | - main 11 | jobs: 12 | run-tests: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Create kind cluster 18 | uses: helm/kind-action@v1.8.0 19 | 20 | - name: Deploy From Source 21 | uses: hiberbee/github-action-skaffold@1.27.0 22 | with: 23 | command: run 24 | 25 | - name: Wait For Pods 26 | timeout-minutes: 20 27 | run: | 28 | set -x 29 | kubectl wait --for=condition=available --timeout=10m deployment/adservice 30 | kubectl wait --for=condition=available --timeout=10m deployment/cartservice 31 | kubectl wait --for=condition=available --timeout=10m deployment/checkoutservice 32 | kubectl wait --for=condition=available --timeout=10m deployment/currencyservice 33 | kubectl wait --for=condition=available --timeout=10m deployment/emailservice 34 | kubectl wait --for=condition=available --timeout=10m deployment/frontend 35 | kubectl wait --for=condition=available --timeout=10m deployment/loadgenerator 36 | kubectl wait --for=condition=available --timeout=10m deployment/paymentservice 37 | kubectl wait --for=condition=available --timeout=10m deployment/productcatalogservice 38 | kubectl wait --for=condition=available --timeout=10m deployment/recommendationservice 39 | kubectl wait --for=condition=available --timeout=10m deployment/shippingservice 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | pkg/ 3 | .DS_Store 4 | *.pyc 5 | *.swp 6 | *~ 7 | venv 8 | node_modules 9 | .vscode/ 10 | .vs/ 11 | .idea 12 | .skaffold-*.yaml 13 | .kubernetes-manifests-*/ 14 | .project 15 | .eclipse.buildship.core.prefs 16 | 17 | # ignore auto-generated k8s manifests 18 | release/*.yaml 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ### Fork 7 | 8 | In the interest of keeping this repository clean and manageable, you should 9 | work from a fork. To create a fork, click the 'Fork' button at the top of the 10 | repository, then clone the fork locally using `git clone 11 | git@https://github.com//microservices-demo`. 12 | 13 | You should also add this repository as an "upstream" repo to your local copy, 14 | in order to keep it up to date. You can add this as a remote like so: 15 | 16 | `git remote add upstream https://github.com/signalfx/microservices-demo.git` 17 | 18 | Verify that the upstream exists: 19 | 20 | `git remote -v` 21 | 22 | To update your fork, fetch the upstream repo's branches and commits, then merge 23 | your `main` with upstream's `main`: 24 | 25 | ``` 26 | git fetch upstream 27 | git checkout main 28 | git merge upstream/main 29 | ``` 30 | 31 | Remember to always work in a branch of your local copy, as you might otherwise 32 | have to contend with conflicts in `main`. 33 | 34 | 35 | ## Creating a PR 36 | 37 | Checkout a new branch, make modifications, build locally, and push the branch to your fork 38 | to open a new PR: 39 | 40 | ```shell 41 | $ git checkout -b myfeature 42 | # edit 43 | # verify that all applications are running well and emitting expected telemetry 44 | $ git add . 45 | $ git commit -m "My commit mesage" 46 | $ git push --set-upstream origin myfeature 47 | ``` 48 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # This configuration file is used to build and deploy the app into a 2 | # GKE cluster using Google Cloud Build. 3 | # 4 | # PREREQUISITES: 5 | # - Cloud Build service account must have role: "Kubernetes Engine Developer" 6 | 7 | # USAGE: 8 | # GCP zone and GKE target cluster must be specified as substitutions 9 | # Example invocation: 10 | # `gcloud builds submit --config=cloudbuild.yaml --substitutions=_ZONE=us-central1-b,_CLUSTER=demo-app-staging .` 11 | 12 | steps: 13 | - id: 'Deploy application to cluster' 14 | name: 'gcr.io/k8s-skaffold/skaffold:v0.20.0' 15 | entrypoint: 'bash' 16 | args: 17 | - '-c' 18 | - > 19 | gcloud container clusters get-credentials --zone=$_ZONE $_CLUSTER; 20 | skaffold run -f=skaffold.yaml --default-repo=gcr.io/$PROJECT_ID; 21 | 22 | # Add more power, and more time, for heavy Skaffold build 23 | timeout: '3600s' 24 | options: 25 | machineType: 'N1_HIGHCPU_8' -------------------------------------------------------------------------------- /docs/development-principles.md: -------------------------------------------------------------------------------- 1 | # Development Principles 2 | 3 | > **Note:** This document outlines guidances behind some development decisions 4 | > behind the Hipster Shop demo application. 5 | 6 | ### Minimal configuration 7 | 8 | Running the demo locally or on GCP should require minimal to no 9 | configuration unless absolutely necessary to run critical parts of the demo. 10 | 11 | Configuration that takes multiple steps, especially such as creating service 12 | accounts should be avoided. 13 | 14 | ### App must work well outside GCP 15 | 16 | Demo application should work reasonably well when it is not deployed to GCP 17 | services. The experience of running the application locally or on GCP should 18 | be close. 19 | 20 | For example: 21 | - OpenCensus prints the traces to stdout when it cannot connect to GCP. 22 | - Stackdriver Debugging tries connecting to GCP multiple times, eventually gives 23 | up. 24 | 25 | ### Running on GCP must not reduce functionality 26 | 27 | Running the demo on the GCP must not reduce/lose any of the capabilities 28 | developers have when running locally. 29 | 30 | For example: Logs should still be printed to stdout/stderr even though logs are 31 | uploaded to Stackdriver Logging when on GCP, so that developers can use "kubectl 32 | logs" to diagnose each container. 33 | 34 | ### Microservice implementations should not be complex 35 | 36 | Each service should provide a minimal implementation and try to avoid 37 | unnecessary code and logic that's not executed. 38 | 39 | Keep in mind that any service implementation is a decent example of “a GRPC 40 | application that runs on Kubernetes”. Keeping the source code short and 41 | navigable will serve this purpose. 42 | 43 | It is okay to have intentional inefficiencies in the code as they help 44 | illustrate the capabilities of profiling and diagnostics offerings. 45 | -------------------------------------------------------------------------------- /docs/img/architecture-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/docs/img/architecture-diagram.png -------------------------------------------------------------------------------- /docs/img/online-boutique-frontend-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/docs/img/online-boutique-frontend-1.png -------------------------------------------------------------------------------- /docs/img/online-boutique-frontend-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/docs/img/online-boutique-frontend-2.png -------------------------------------------------------------------------------- /hack/README.md: -------------------------------------------------------------------------------- 1 | ## `hack/` 2 | 3 | This directory provides scripts for building and pushing Docker images, and tagging new demo 4 | releases. 5 | 6 | ### env variables 7 | 8 | - `TAG` - git release tag / Docker tag. 9 | - `REPO_PREFIX` - Docker repo prefix to push images. Format: `$user/$project`. Resulting images will be of the 10 | format `$user/$project/$svcname:$tag` (where `svcname` = `adservice`, `cartservice`, 11 | etc.) 12 | 13 | ### scripts 14 | 15 | 1. `./make-docker-images.sh`: builds and pushes images to the specified Docker repository. 16 | 2. `./make-release-artifacts.sh`: generates a combined YAML file with image $TAG at: 17 | `./release/kubernetes-manifests/demo.yaml`. 18 | 3. `./make-release.sh`: runs scripts 1 and 2, then runs `git tag` / pushes updated manifests to master. 19 | -------------------------------------------------------------------------------- /hack/license_header.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/make-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Builds and pushes docker image for each demo microservice. 18 | 19 | set -euo pipefail 20 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 21 | 22 | log() { echo "$1" >&2; } 23 | 24 | TAG="${TAG:--}" 25 | REPO_PREFIX="${REPO_PREFIX:-quay.io/signalfuse/microservices-demo-}" 26 | 27 | while IFS= read -d $'\0' -r dir; do 28 | # build image 29 | svcname="$(basename "${dir}")" 30 | image="${REPO_PREFIX}$svcname" 31 | ( 32 | cd "${dir}" 33 | log "Building: ${image}" 34 | docker build -t "${image}:latest" . 35 | 36 | log "Pushing: ${image}" 37 | docker push "${image}:latest" 38 | 39 | if [ ${TAG} != "-" ] 40 | then 41 | docker tag "${image}:latest" "${image}:$TAG" 42 | docker push "${image}:$TAG" 43 | fi 44 | ) 45 | done < <(find "${SCRIPTDIR}/../src" -mindepth 1 -maxdepth 1 -type d -print0) 46 | 47 | log "Successfully built and pushed all images." 48 | -------------------------------------------------------------------------------- /hack/make-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script creates a new release by: 18 | # - 1. building/pushing images 19 | # - 2. injecting tags into YAML manifests 20 | # - 3. creating a new git tag 21 | # - 4. pushing the tag/commit to master. 22 | 23 | set -euo pipefail 24 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 25 | [[ -n "${DEBUG:-}" ]] && set -x 26 | 27 | log() { echo "$1" >&2; } 28 | fail() { log "$1"; exit 1; } 29 | 30 | TAG="${TAG:?TAG env variable must be specified}" 31 | REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified e.g. gcr.io\/google-samples\/microservices-demo}" 32 | 33 | if [[ "$TAG" != v* ]]; then 34 | fail "\$TAG must start with 'v', e.g. v0.1.0 (got: $TAG)" 35 | fi 36 | 37 | # build and push images 38 | "${SCRIPTDIR}"/make-docker-images.sh 39 | 40 | # update yaml 41 | "${SCRIPTDIR}"/make-release-artifacts.sh 42 | 43 | # create git release / push to new branch 44 | git checkout -b "release/${TAG}" 45 | git add "${SCRIPTDIR}/../release/" 46 | git commit --allow-empty -m "Release $TAG" 47 | log "Pushing k8s manifests to release/${TAG}..." 48 | git tag "$TAG" 49 | git push --set-upstream origin "release/${TAG}" 50 | git push --tags 51 | 52 | log "Successfully tagged release $TAG." 53 | -------------------------------------------------------------------------------- /istio-manifests/frontend-gateway.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: networking.istio.io/v1alpha3 16 | kind: Gateway 17 | metadata: 18 | name: frontend-gateway 19 | spec: 20 | selector: 21 | istio: ingressgateway # use Istio default gateway implementation 22 | servers: 23 | - port: 24 | number: 80 25 | name: http 26 | protocol: HTTP 27 | hosts: 28 | - "*" 29 | --- 30 | apiVersion: networking.istio.io/v1alpha3 31 | kind: VirtualService 32 | metadata: 33 | name: frontend-ingress 34 | spec: 35 | hosts: 36 | - "*" 37 | gateways: 38 | - frontend-gateway 39 | http: 40 | - route: 41 | - destination: 42 | host: frontend 43 | port: 44 | number: 80 45 | -------------------------------------------------------------------------------- /istio-manifests/frontend.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: networking.istio.io/v1alpha3 16 | kind: VirtualService 17 | metadata: 18 | name: frontend 19 | spec: 20 | hosts: 21 | - "frontend.default.svc.cluster.local" 22 | http: 23 | - route: 24 | - destination: 25 | host: frontend 26 | port: 27 | number: 80 28 | -------------------------------------------------------------------------------- /istio-manifests/whitelist-egress-googleapis.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: networking.istio.io/v1alpha3 16 | kind: ServiceEntry 17 | metadata: 18 | name: whitelist-egress-googleapis 19 | spec: 20 | hosts: 21 | - "accounts.google.com" # Used to get token 22 | - "*.googleapis.com" 23 | ports: 24 | - number: 80 25 | protocol: HTTP 26 | name: http 27 | - number: 443 28 | protocol: HTTPS 29 | name: https 30 | --- 31 | apiVersion: networking.istio.io/v1alpha3 32 | kind: ServiceEntry 33 | metadata: 34 | name: whitelist-egress-google-metadata 35 | spec: 36 | hosts: 37 | - metadata.google.internal 38 | addresses: 39 | - 169.254.169.254 # GCE metadata server 40 | ports: 41 | - number: 80 42 | name: http 43 | protocol: HTTP 44 | - number: 443 45 | name: https 46 | protocol: HTTPS 47 | -------------------------------------------------------------------------------- /kubernetes-manifests/README.md: -------------------------------------------------------------------------------- 1 | # ./kubernetes-manifests 2 | 3 | :warning: Kubernetes manifests provided in this directory are not directly 4 | deployable to a cluster. They are meant to be used with `skaffold` command to 5 | insert the correct `image:` tags. 6 | 7 | Use the manifests in [/release](/release) directory which are configured with 8 | pre-built public images. 9 | -------------------------------------------------------------------------------- /kubernetes-manifests/adservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: adservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: adservice 23 | template: 24 | metadata: 25 | labels: 26 | app: adservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | tolerations: 30 | nodeSelector: 31 | containers: 32 | - name: server 33 | image: adservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - containerPort: 9555 37 | env: 38 | - name: PORT 39 | value: '9555' 40 | - name: OTEL_SERVICE_NAME 41 | value: adservice 42 | - name: NODE_IP 43 | valueFrom: 44 | fieldRef: 45 | fieldPath: status.hostIP 46 | - name: JAVA_TOOL_OPTIONS 47 | value: -javaagent:/opt/sfx/splunk-otel-javaagent-all.jar 48 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 49 | value: 'http://$(NODE_IP):4317' 50 | - name: OTEL_TRACES_EXPORTER 51 | value: otlp 52 | resources: 53 | requests: 54 | cpu: 200m 55 | memory: 300Mi 56 | limits: 57 | cpu: 200m 58 | memory: 300Mi 59 | readinessProbe: 60 | initialDelaySeconds: 60 61 | periodSeconds: 25 62 | exec: 63 | command: ['/bin/grpc_health_probe', '-addr=:9555'] 64 | livenessProbe: 65 | initialDelaySeconds: 60 66 | periodSeconds: 30 67 | exec: 68 | command: ['/bin/grpc_health_probe', '-addr=:9555'] 69 | --- 70 | apiVersion: v1 71 | kind: Service 72 | metadata: 73 | name: adservice 74 | spec: 75 | type: ClusterIP 76 | selector: 77 | app: adservice 78 | ports: 79 | - name: grpc 80 | port: 9555 81 | targetPort: 9555 82 | -------------------------------------------------------------------------------- /kubernetes-manifests/cartservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: cartservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: cartservice 23 | template: 24 | metadata: 25 | labels: 26 | app: cartservice 27 | spec: 28 | terminationGracePeriodSeconds: 4 29 | tolerations: 30 | nodeSelector: 31 | containers: 32 | - name: server 33 | image: cartservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - containerPort: 7070 37 | env: 38 | - name: REDIS_ADDR 39 | value: "redis-cart:6379" 40 | - name: PORT 41 | value: "7070" 42 | - name: LISTEN_ADDR 43 | value: "0.0.0.0" 44 | - name: NODE_IP 45 | valueFrom: 46 | fieldRef: 47 | fieldPath: status.hostIP 48 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 49 | value: "http://$(NODE_IP):4318" 50 | - name: OTEL_SERVICE_NAME 51 | value: "cartservice" 52 | - name: OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES 53 | value: "Cartservice" 54 | - name: EXTERNAL_DB_NAME 55 | value: "Galactus.Postgres" 56 | - name: EXTERNAL_DB_ACCESS_RATE 57 | value: "0.75" 58 | - name: EXTERNAL_DB_MAX_DURATION_MILLIS 59 | value: "750" 60 | - name: EXTERNAL_DB_ERROR_RATE 61 | value: "0.0" 62 | - name: FIX_EXCESSIVE_ALLOCATION 63 | value: "true" 64 | - name: FIX_SLOW_LEAK 65 | value: "true" 66 | - name: OPTIMIZE_CPU 67 | value: "true" 68 | - name: OPTIMIZE_BLOCKING 69 | value: "true" 70 | resources: 71 | requests: 72 | cpu: 70m 73 | memory: 64Mi 74 | limits: 75 | cpu: 70m 76 | memory: 128Mi 77 | readinessProbe: 78 | initialDelaySeconds: 15 79 | exec: 80 | command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] 81 | livenessProbe: 82 | initialDelaySeconds: 15 83 | periodSeconds: 10 84 | exec: 85 | command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] 86 | --- 87 | apiVersion: v1 88 | kind: Service 89 | metadata: 90 | name: cartservice 91 | spec: 92 | type: ClusterIP 93 | selector: 94 | app: cartservice 95 | ports: 96 | - name: grpc 97 | port: 7070 98 | targetPort: 7070 99 | -------------------------------------------------------------------------------- /kubernetes-manifests/checkoutservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: checkoutservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: checkoutservice 23 | template: 24 | metadata: 25 | labels: 26 | app: checkoutservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | containers: 31 | - name: server 32 | image: checkoutservice 33 | imagePullPolicy: IfNotPresent 34 | ports: 35 | - containerPort: 5050 36 | readinessProbe: 37 | exec: 38 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 39 | livenessProbe: 40 | exec: 41 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 42 | env: 43 | - name: PORT 44 | value: "5050" 45 | - name: PRODUCT_CATALOG_SERVICE_ADDR 46 | value: "productcatalogservice:3550" 47 | - name: SHIPPING_SERVICE_ADDR 48 | value: "shippingservice:50051" 49 | - name: PAYMENT_SERVICE_ADDR 50 | value: "paymentservice:50051" 51 | - name: EMAIL_SERVICE_ADDR 52 | value: "emailservice:5000" 53 | - name: CURRENCY_SERVICE_ADDR 54 | value: "currencyservice:7000" 55 | - name: CART_SERVICE_ADDR 56 | value: "cartservice:7070" 57 | - name: NODE_IP 58 | valueFrom: 59 | fieldRef: 60 | fieldPath: status.hostIP 61 | - name: SIGNALFX_ENDPOINT_URL 62 | # value: "http://zipkin.default:9411/api/v2/spans" 63 | value: "http://$(NODE_IP):9411/api/v2/spans" 64 | - name: MAX_RETRY_ATTEMPTS 65 | value: "20" 66 | - name: RETRY_INITIAL_SLEEP_MILLIS 67 | value: "25" 68 | resources: 69 | requests: 70 | cpu: 70m 71 | memory: 64Mi 72 | limits: 73 | cpu: 70m 74 | memory: 128Mi 75 | --- 76 | apiVersion: v1 77 | kind: Service 78 | metadata: 79 | name: checkoutservice 80 | spec: 81 | type: ClusterIP 82 | selector: 83 | app: checkoutservice 84 | ports: 85 | - name: grpc 86 | port: 5050 87 | targetPort: 5050 88 | -------------------------------------------------------------------------------- /kubernetes-manifests/currencyservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: currencyservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: currencyservice 23 | template: 24 | metadata: 25 | labels: 26 | app: currencyservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | terminationGracePeriodSeconds: 5 31 | containers: 32 | - name: server 33 | image: currencyservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - name: grpc 37 | containerPort: 7000 38 | env: 39 | - name: PORT 40 | value: "7000" 41 | - name: NODE_IP 42 | valueFrom: 43 | fieldRef: 44 | fieldPath: status.hostIP 45 | - name: SIGNALFX_ENDPOINT_URL 46 | # value: "http://zipkin.default:9411/api/v2/spans" 47 | value: "http://$(NODE_IP):9411/api/v2/spans" 48 | readinessProbe: 49 | exec: 50 | command: ["/bin/grpc_health_probe", "-addr=:7000"] 51 | livenessProbe: 52 | exec: 53 | command: ["/bin/grpc_health_probe", "-addr=:7000"] 54 | resources: 55 | requests: 56 | cpu: 70m 57 | memory: 64Mi 58 | limits: 59 | cpu: 70m 60 | memory: 128Mi 61 | --- 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: currencyservice 66 | spec: 67 | type: ClusterIP 68 | selector: 69 | app: currencyservice 70 | ports: 71 | - name: grpc 72 | port: 7000 73 | targetPort: 7000 74 | -------------------------------------------------------------------------------- /kubernetes-manifests/emailservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: emailservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: emailservice 23 | template: 24 | metadata: 25 | labels: 26 | app: emailservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | terminationGracePeriodSeconds: 5 31 | containers: 32 | - name: server 33 | image: emailservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - containerPort: 8080 37 | env: 38 | - name: PORT 39 | value: "8080" 40 | - name: NODE_IP 41 | valueFrom: 42 | fieldRef: 43 | fieldPath: status.hostIP 44 | - name: SIGNALFX_ENDPOINT_URL 45 | # value: "http://zipkin.default:9411/api/v2/spans" 46 | value: "http://$(NODE_IP):9411/api/v2/spans" 47 | readinessProbe: 48 | periodSeconds: 5 49 | exec: 50 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 51 | livenessProbe: 52 | periodSeconds: 5 53 | exec: 54 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 55 | resources: 56 | requests: 57 | cpu: 70m 58 | memory: 64Mi 59 | limits: 60 | cpu: 70m 61 | memory: 128Mi 62 | --- 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: emailservice 67 | spec: 68 | type: ClusterIP 69 | selector: 70 | app: emailservice 71 | ports: 72 | - name: grpc 73 | port: 5000 74 | targetPort: 8080 75 | -------------------------------------------------------------------------------- /kubernetes-manifests/frontend.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: frontend 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: frontend 23 | template: 24 | metadata: 25 | labels: 26 | app: frontend 27 | annotations: 28 | sidecar.istio.io/rewriteAppHTTPProbers: "true" 29 | spec: 30 | tolerations: 31 | nodeSelector: 32 | containers: 33 | - name: server 34 | image: frontend 35 | imagePullPolicy: IfNotPresent 36 | ports: 37 | - containerPort: 8080 38 | readinessProbe: 39 | initialDelaySeconds: 10 40 | httpGet: 41 | path: "/_healthz" 42 | port: 8080 43 | httpHeaders: 44 | - name: "Cookie" 45 | value: "shop_session-id=x-readiness-probe" 46 | livenessProbe: 47 | initialDelaySeconds: 10 48 | httpGet: 49 | path: "/_healthz" 50 | port: 8080 51 | httpHeaders: 52 | - name: "Cookie" 53 | value: "shop_session-id=x-liveness-probe" 54 | env: 55 | - name: PORT 56 | value: "8080" 57 | - name: PRODUCT_CATALOG_SERVICE_ADDR 58 | value: "productcatalogservice:3550" 59 | - name: CURRENCY_SERVICE_ADDR 60 | value: "currencyservice:7000" 61 | - name: CART_SERVICE_ADDR 62 | value: "cartservice:7070" 63 | - name: RECOMMENDATION_SERVICE_ADDR 64 | value: "recommendationservice:8080" 65 | - name: SHIPPING_SERVICE_ADDR 66 | value: "shippingservice:50051" 67 | - name: CHECKOUT_SERVICE_ADDR 68 | value: "checkoutservice:5050" 69 | - name: AD_SERVICE_ADDR 70 | value: "adservice:9555" 71 | - name: RUM_REALM 72 | - name: RUM_AUTH 73 | - name: RUM_APP_NAME 74 | - name: RUM_ENVIRONMENT 75 | - name: RUM_DEBUG 76 | - name: ENV_PLATFORM 77 | value: "gcp" 78 | - name: NODE_IP 79 | valueFrom: 80 | fieldRef: 81 | fieldPath: status.hostIP 82 | - name: SIGNALFX_ENDPOINT_URL 83 | # value: "http://zipkin.default:9411/api/v2/spans" 84 | value: "http://$(NODE_IP):9411/api/v2/spans" 85 | - name: SIGNALFX_SERVER_TIMING_CONTEXT 86 | value: "true" 87 | resources: 88 | requests: 89 | cpu: 70m 90 | memory: 64Mi 91 | limits: 92 | cpu: 70m 93 | memory: 128Mi 94 | --- 95 | apiVersion: v1 96 | kind: Service 97 | metadata: 98 | name: frontend 99 | spec: 100 | type: ClusterIP 101 | selector: 102 | app: frontend 103 | ports: 104 | - name: http 105 | port: 80 106 | targetPort: 8080 107 | --- 108 | apiVersion: v1 109 | kind: Service 110 | metadata: 111 | name: frontend-external 112 | annotations: 113 | service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0 114 | service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5" 115 | service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "3" 116 | service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2" 117 | external-dns.alpha.kubernetes.io/hostname: demo.${DOMAIN} 118 | spec: 119 | type: LoadBalancer 120 | selector: 121 | app: frontend 122 | ports: 123 | - name: http 124 | port: 80 125 | targetPort: 8080 126 | -------------------------------------------------------------------------------- /kubernetes-manifests/loadgenerator.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: loadgenerator 18 | spec: 19 | selector: 20 | matchLabels: 21 | app: loadgenerator 22 | replicas: 1 23 | template: 24 | metadata: 25 | labels: 26 | app: loadgenerator 27 | annotations: 28 | sidecar.istio.io/rewriteAppHTTPProbers: "true" 29 | spec: 30 | tolerations: 31 | nodeSelector: 32 | terminationGracePeriodSeconds: 5 33 | restartPolicy: Always 34 | containers: 35 | - name: main 36 | ports: 37 | - containerPort: 8089 38 | image: loadgenerator 39 | imagePullPolicy: IfNotPresent 40 | env: 41 | - name: FRONTEND_ADDR 42 | value: "frontend:80" 43 | - name: USERS 44 | value: "10" 45 | resources: 46 | requests: 47 | cpu: 70m 48 | memory: 128Mi 49 | limits: 50 | cpu: 70m 51 | memory: 128Mi 52 | --- 53 | apiVersion: v1 54 | kind: Service 55 | metadata: 56 | name: loadgenerator 57 | annotations: 58 | service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0 59 | service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5" 60 | service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "3" 61 | service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2" 62 | external-dns.alpha.kubernetes.io/hostname: demo-load.${DOMAIN} 63 | spec: 64 | type: LoadBalancer 65 | selector: 66 | app: loadgenerator 67 | ports: 68 | - name: http 69 | port: 8089 70 | targetPort: 8089 71 | -------------------------------------------------------------------------------- /kubernetes-manifests/paymentservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: paymentservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: paymentservice 23 | template: 24 | metadata: 25 | labels: 26 | app: paymentservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | terminationGracePeriodSeconds: 5 31 | containers: 32 | - name: server 33 | image: paymentservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - containerPort: 50051 37 | env: 38 | - name: PORT 39 | value: "50051" 40 | - name: NODE_IP 41 | valueFrom: 42 | fieldRef: 43 | fieldPath: status.hostIP 44 | - name: SIGNALFX_ENDPOINT_URL 45 | # value: "http://zipkin.default:9411/api/v2/spans" 46 | value: "http://$(NODE_IP):9411/api/v2/spans" 47 | - name: API_TOKEN_FAILURE_RATE 48 | value: "0.75" 49 | - name: SERIALIZATION_FAILURE_RATE 50 | value: "0.0" 51 | - name: SUCCESS_PAYMENT_SERVICE_DURATION_MILLIS 52 | value: "200" 53 | - name: ERROR_PAYMENT_SERVICE_DURATION_MILLIS 54 | value: "500" 55 | readinessProbe: 56 | exec: 57 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 58 | livenessProbe: 59 | exec: 60 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 61 | resources: 62 | requests: 63 | cpu: 80m 64 | memory: 64Mi 65 | limits: 66 | cpu: 80m 67 | memory: 128Mi 68 | --- 69 | apiVersion: v1 70 | kind: Service 71 | metadata: 72 | name: paymentservice 73 | spec: 74 | type: ClusterIP 75 | selector: 76 | app: paymentservice 77 | ports: 78 | - name: grpc 79 | port: 50051 80 | targetPort: 50051 81 | -------------------------------------------------------------------------------- /kubernetes-manifests/productcatalogservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: productcatalogservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: productcatalogservice 23 | template: 24 | metadata: 25 | labels: 26 | app: productcatalogservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | terminationGracePeriodSeconds: 5 31 | containers: 32 | - name: server 33 | image: productcatalogservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - containerPort: 3550 37 | env: 38 | - name: PORT 39 | value: "3550" 40 | - name: NODE_IP 41 | valueFrom: 42 | fieldRef: 43 | fieldPath: status.hostIP 44 | - name: SIGNALFX_ENDPOINT_URL 45 | value: "http://$(NODE_IP):9411/api/v2/spans" 46 | # value: "http://zipkin.default:9411/api/v2/spans" 47 | readinessProbe: 48 | exec: 49 | command: ["/bin/grpc_health_probe", "-addr=:3550"] 50 | livenessProbe: 51 | exec: 52 | command: ["/bin/grpc_health_probe", "-addr=:3550"] 53 | resources: 54 | requests: 55 | cpu: 50m 56 | memory: 64Mi 57 | limits: 58 | cpu: 50m 59 | memory: 128Mi 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: productcatalogservice 65 | spec: 66 | type: ClusterIP 67 | selector: 68 | app: productcatalogservice 69 | ports: 70 | - name: grpc 71 | port: 3550 72 | targetPort: 3550 73 | -------------------------------------------------------------------------------- /kubernetes-manifests/recommendationservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: recommendationservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: recommendationservice 23 | template: 24 | metadata: 25 | labels: 26 | app: recommendationservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | terminationGracePeriodSeconds: 5 31 | containers: 32 | - name: server 33 | image: recommendationservice 34 | imagePullPolicy: IfNotPresent 35 | ports: 36 | - containerPort: 8080 37 | readinessProbe: 38 | periodSeconds: 5 39 | exec: 40 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 41 | livenessProbe: 42 | periodSeconds: 5 43 | exec: 44 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 45 | env: 46 | - name: PORT 47 | value: "8080" 48 | - name: PRODUCT_CATALOG_SERVICE_ADDR 49 | value: "productcatalogservice:3550" 50 | - name: NODE_IP 51 | valueFrom: 52 | fieldRef: 53 | fieldPath: status.hostIP 54 | - name: SIGNALFX_ENDPOINT_URL 55 | value: "http://$(NODE_IP):9411/api/v2/spans" 56 | # value: "http://zipkin.default:9411/api/v2/spans" 57 | resources: 58 | requests: 59 | cpu: 70m 60 | memory: 128Mi 61 | limits: 62 | cpu: 70m 63 | memory: 256Mi 64 | --- 65 | apiVersion: v1 66 | kind: Service 67 | metadata: 68 | name: recommendationservice 69 | spec: 70 | type: ClusterIP 71 | selector: 72 | app: recommendationservice 73 | ports: 74 | - name: grpc 75 | port: 8080 76 | targetPort: 8080 77 | -------------------------------------------------------------------------------- /kubernetes-manifests/redis.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: redis-cart 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: redis-cart 23 | template: 24 | metadata: 25 | labels: 26 | app: redis-cart 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | containers: 31 | - name: redis 32 | image: redis:alpine 33 | imagePullPolicy: IfNotPresent 34 | ports: 35 | - containerPort: 6379 36 | readinessProbe: 37 | periodSeconds: 5 38 | tcpSocket: 39 | port: 6379 40 | livenessProbe: 41 | periodSeconds: 5 42 | tcpSocket: 43 | port: 6379 44 | volumeMounts: 45 | - mountPath: /data 46 | name: redis-data 47 | resources: 48 | limits: 49 | memory: 256Mi 50 | cpu: 70m 51 | requests: 52 | cpu: 70m 53 | memory: 200Mi 54 | volumes: 55 | - name: redis-data 56 | emptyDir: {} 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: redis-cart 62 | spec: 63 | type: ClusterIP 64 | selector: 65 | app: redis-cart 66 | ports: 67 | - name: redis 68 | port: 6379 69 | targetPort: 6379 70 | -------------------------------------------------------------------------------- /kubernetes-manifests/shippingservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: shippingservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: shippingservice 23 | template: 24 | metadata: 25 | labels: 26 | app: shippingservice 27 | spec: 28 | tolerations: 29 | nodeSelector: 30 | containers: 31 | - name: server 32 | image: shippingservice 33 | imagePullPolicy: IfNotPresent 34 | ports: 35 | - containerPort: 50051 36 | env: 37 | - name: PORT 38 | value: "50051" 39 | - name: NODE_IP 40 | valueFrom: 41 | fieldRef: 42 | fieldPath: status.hostIP 43 | - name: SIGNALFX_ENDPOINT_URL 44 | value: "http://$(NODE_IP):9411/api/v2/spans" 45 | # value: "http://zipkin.default:9411/api/v2/spans" 46 | readinessProbe: 47 | periodSeconds: 5 48 | exec: 49 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 50 | livenessProbe: 51 | exec: 52 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 53 | resources: 54 | requests: 55 | cpu: 50m 56 | memory: 64Mi 57 | limits: 58 | cpu: 50m 59 | memory: 128Mi 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: shippingservice 65 | spec: 66 | type: ClusterIP 67 | selector: 68 | app: shippingservice 69 | ports: 70 | - name: grpc 71 | port: 50051 72 | targetPort: 50051 73 | -------------------------------------------------------------------------------- /pb/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: skaffold/v2beta26 16 | kind: Config 17 | metadata: 18 | name: app 19 | build: 20 | artifacts: 21 | # image tags are relative; to specify an image repo, you must provide a 22 | # "default repo" using one of the methods described here: 23 | # https://skaffold.dev/docs/concepts/#image-repository-handling 24 | - image: emailservice 25 | context: src/emailservice 26 | - image: productcatalogservice 27 | context: src/productcatalogservice 28 | - image: recommendationservice 29 | context: src/recommendationservice 30 | - image: shippingservice 31 | context: src/shippingservice 32 | - image: checkoutservice 33 | context: src/checkoutservice 34 | - image: paymentservice 35 | context: src/paymentservice 36 | - image: currencyservice 37 | context: src/currencyservice 38 | - image: cartservice 39 | context: src/cartservice 40 | - image: frontend 41 | context: src/frontend 42 | - image: adservice 43 | context: src/adservice 44 | tagPolicy: 45 | gitCommit: {} 46 | deploy: 47 | statusCheckDeadlineSeconds: 1200 48 | kubectl: 49 | manifests: 50 | - ./kubernetes-manifests/adservice.yaml 51 | - ./kubernetes-manifests/cartservice.yaml 52 | - ./kubernetes-manifests/checkoutservice.yaml 53 | - ./kubernetes-manifests/currencyservice.yaml 54 | - ./kubernetes-manifests/emailservice.yaml 55 | - ./kubernetes-manifests/frontend.yaml 56 | - ./kubernetes-manifests/paymentservice.yaml 57 | - ./kubernetes-manifests/productcatalogservice.yaml 58 | - ./kubernetes-manifests/recommendationservice.yaml 59 | - ./kubernetes-manifests/redis.yaml 60 | - ./kubernetes-manifests/shippingservice.yaml 61 | 62 | --- 63 | 64 | apiVersion: skaffold/v2beta26 65 | kind: Config 66 | metadata: 67 | name: loadgenerator 68 | requires: 69 | - configs: [app] 70 | build: 71 | artifacts: 72 | - image: loadgenerator 73 | context: src/loadgenerator 74 | deploy: 75 | kubectl: 76 | manifests: 77 | - ./kubernetes-manifests/loadgenerator.yaml -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Go: for the time being we are not checking in the vendor/ directories to git 2 | # to prevent the repo from getting larger forever. In each Go service, you can 3 | # run "dep ensure --vendor-only" to download the dependencies to vendor/ based 4 | # on the Gopkg.{toml,lock} files in that directory. 5 | vendor/ 6 | -------------------------------------------------------------------------------- /src/adservice/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | .classpath 5 | .settings/** 6 | .gradle/** 7 | .idea/** 8 | build/** 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/adservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk8:alpine-slim as base 2 | 3 | FROM base as builder 4 | 5 | WORKDIR /app 6 | 7 | COPY ["build.gradle", "gradlew", "./"] 8 | COPY gradle gradle 9 | RUN chmod +x gradlew 10 | RUN ./gradlew downloadRepos 11 | 12 | COPY . . 13 | RUN chmod +x gradlew 14 | RUN ./gradlew installDist 15 | 16 | RUN apk --update add curl 17 | 18 | WORKDIR /tmp 19 | RUN curl -L https://github.com/signalfx/splunk-otel-java/releases/download/v1.17.0/splunk-otel-javaagent-all.jar -o splunk-otel-javaagent-all.jar 20 | 21 | FROM base 22 | 23 | RUN mkdir -p /opt/sfx 24 | COPY --from=builder /tmp/splunk-otel-javaagent-all.jar /opt/sfx/splunk-otel-javaagent-all.jar 25 | 26 | RUN GRPC_HEALTH_PROBE_VERSION=v0.3.1 && \ 27 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 28 | chmod +x /bin/grpc_health_probe 29 | 30 | WORKDIR /app 31 | COPY --from=builder /app . 32 | 33 | EXPOSE 9555 34 | ENTRYPOINT ["/app/build/install/hipstershop/bin/AdService"] 35 | -------------------------------------------------------------------------------- /src/adservice/README.md: -------------------------------------------------------------------------------- 1 | # Ad Service 2 | 3 | The Ad service provides advertisement based on context keys. If no context keys are provided then it returns random ads. 4 | 5 | ## Building locally 6 | 7 | The Ad service uses gradlew to compile/install/distribute. Gradle wrapper is already part of the source code. To build Ad Service, run: 8 | 9 | ``` 10 | ./gradlew installDist 11 | ``` 12 | It will create executable script src/adservice/build/install/hipstershop/bin/AdService 13 | 14 | ### Upgrading gradle version 15 | If you need to upgrade the version of gradle then run 16 | 17 | ``` 18 | ./gradlew wrapper --gradle-version 19 | ``` 20 | 21 | ## Building docker image 22 | 23 | From `src/adservice/`, run: 24 | 25 | ``` 26 | docker build ./ 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /src/adservice/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.google.protobuf' version '0.8.16' 3 | id 'com.github.sherter.google-java-format' version '0.8' 4 | id 'idea' 5 | id 'application' 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | mavenLocal() 11 | } 12 | 13 | description = 'Ad Service' 14 | group = "adservice" 15 | version = "0.1.0-SNAPSHOT" 16 | 17 | def grpcVersion = "1.52.1" 18 | def protocVersion = "3.21.12" 19 | 20 | tasks.withType(JavaCompile) { 21 | sourceCompatibility = JavaVersion.VERSION_1_8 22 | targetCompatibility = JavaVersion.VERSION_1_8 23 | } 24 | 25 | ext { 26 | speed = project.hasProperty('speed') ? project.getProperty('speed') : false 27 | offlineCompile = new File("$buildDir/output/lib") 28 | } 29 | 30 | dependencies { 31 | if (speed) { 32 | implementation fileTree(dir: offlineCompile, include: '*.jar') 33 | } else { 34 | implementation "com.google.api.grpc:proto-google-common-protos:1.17.0", 35 | "io.grpc:grpc-protobuf:${grpcVersion}", 36 | "io.grpc:grpc-stub:${grpcVersion}", 37 | "io.grpc:grpc-netty:${grpcVersion}", 38 | "io.grpc:grpc-services:${grpcVersion}", 39 | "org.apache.logging.log4j:log4j-core:2.17.1" 40 | 41 | runtimeOnly "com.fasterxml.jackson.core:jackson-core:2.14.0-rc3", 42 | "com.fasterxml.jackson.core:jackson-databind:2.14.0-rc3", 43 | "io.netty:netty-tcnative-boringssl-static:2.0.43.Final" 44 | } 45 | } 46 | 47 | protobuf { 48 | protoc { 49 | artifact = "com.google.protobuf:protoc:${protocVersion}" 50 | } 51 | plugins { 52 | grpc { 53 | artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" 54 | } 55 | } 56 | generateProtoTasks { 57 | all()*.plugins { 58 | grpc {} 59 | } 60 | ofSourceSet('main') 61 | } 62 | } 63 | 64 | googleJavaFormat { 65 | toolVersion '1.7' 66 | } 67 | 68 | // Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. 69 | sourceSets { 70 | main { 71 | java { 72 | srcDirs 'hipstershop' 73 | srcDirs 'build/generated/source/proto/main/java/hipstershop' 74 | srcDirs 'build/generated/source/proto/main/grpc/hipstershop' 75 | } 76 | } 77 | } 78 | 79 | startScripts.enabled = false 80 | 81 | // This to cache dependencies during Docker image building. First build will take time. 82 | // Subsequent build will be incremental. 83 | task downloadRepos(type: Copy) { 84 | from configurations.compile 85 | into offlineCompile 86 | from configurations.runtime 87 | into offlineCompile 88 | } 89 | 90 | task adService(type: CreateStartScripts) { 91 | mainClassName = 'hipstershop.AdService' 92 | applicationName = 'AdService' 93 | outputDir = new File(project.buildDir, 'tmp') 94 | classpath = startScripts.classpath 95 | } 96 | 97 | task adServiceClient(type: CreateStartScripts) { 98 | mainClassName = 'hipstershop.AdServiceClient' 99 | applicationName = 'AdServiceClient' 100 | outputDir = new File(project.buildDir, 'tmp') 101 | classpath = startScripts.classpath 102 | } 103 | 104 | applicationDistribution.into('bin') { 105 | from(adService) 106 | from(adServiceClient) 107 | fileMode = 0755 108 | } 109 | -------------------------------------------------------------------------------- /src/adservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | # protos are needed in adservice folder for compiling during Docker build. 20 | 21 | mkdir -p proto && \ 22 | cp ../../pb/demo.proto src/main/proto 23 | -------------------------------------------------------------------------------- /src/adservice/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/adservice/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/adservice/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/adservice/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /src/adservice/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'hipstershop' 2 | -------------------------------------------------------------------------------- /src/adservice/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/cartservice/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.sh 2 | **/*.bat 3 | tests/ 4 | -------------------------------------------------------------------------------- /src/cartservice/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /obj/* 3 | .vs/*.* 4 | -------------------------------------------------------------------------------- /src/cartservice/ActivitySourceUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace cartservice; 4 | 5 | public class ActivitySourceUtil 6 | { 7 | public static ActivitySource ActivitySource = new ActivitySource("Cartservice"); 8 | } -------------------------------------------------------------------------------- /src/cartservice/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NET_VERSION=8.0 2 | FROM mcr.microsoft.com/dotnet/sdk:${NET_VERSION} as builder 3 | 4 | WORKDIR /app 5 | COPY . . 6 | RUN dotnet restore && \ 7 | dotnet build && \ 8 | dotnet publish -c release -r linux-x64 --no-self-contained -o /cartservice 9 | 10 | # cartservice 11 | FROM mcr.microsoft.com/dotnet/aspnet:${NET_VERSION} 12 | 13 | # Update and fix critical issues on the distribution 14 | RUN apt-get update 15 | RUN apt-get -y dist-upgrade 16 | 17 | ARG GRPC_HEALTH_PROBE_VERSION=v0.4.22 18 | ADD https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 /bin/grpc_health_probe 19 | RUN chmod +x /bin/grpc_health_probe 20 | 21 | WORKDIR /app 22 | COPY --from=builder /cartservice . 23 | COPY --from=builder /app/start . 24 | 25 | # Add the Splunk Distribution of OpenTelemetry .NET 26 | RUN apt-get update && \ 27 | apt-get install -y \ 28 | curl \ 29 | unzip 30 | 31 | ARG SPLUNK_OTEL_VERSION=1.10.0 32 | ENV OTEL_DOTNET_AUTO_HOME=/.splunk-otel-dotnet 33 | ADD https://github.com/signalfx/splunk-otel-dotnet/releases/download/v${SPLUNK_OTEL_VERSION}/splunk-otel-dotnet-install.sh ./ 34 | RUN chmod +x ./splunk-otel-dotnet-install.sh && \ 35 | ./splunk-otel-dotnet-install.sh && \ 36 | rm ./splunk-otel-dotnet-install.sh 37 | 38 | ENV CORECLR_ENABLE_PROFILING=1 39 | ENV CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318} 40 | ENV CORECLR_PROFILER_PATH=/.splunk-otel-dotnet/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so 41 | ENV DOTNET_ADDITIONAL_DEPS=/.splunk-otel-dotnet/AdditionalDeps 42 | ENV DOTNET_SHARED_STORE=/.splunk-otel-dotnet/store 43 | ENV DOTNET_STARTUP_HOOKS=/.splunk-otel-dotnet/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll 44 | ENV OTEL_DOTNET_AUTO_PLUGINS="Splunk.OpenTelemetry.AutoInstrumentation.Plugin, Splunk.OpenTelemetry.AutoInstrumentation" 45 | 46 | ENTRYPOINT ["./cartservice", "start"] 47 | -------------------------------------------------------------------------------- /src/cartservice/HealthImpl.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using cartservice.interfaces; 3 | using Grpc.Core; 4 | using Grpc.Health.V1; 5 | using static Grpc.Health.V1.Health; 6 | 7 | namespace cartservice; 8 | 9 | internal class HealthImpl : HealthBase { 10 | private ICartStore dependency { get; } 11 | public HealthImpl (ICartStore dependency) { 12 | this.dependency = dependency; 13 | } 14 | 15 | public override Task Check(HealthCheckRequest request, ServerCallContext context){ 16 | return Task.FromResult(new HealthCheckResponse { 17 | Status = dependency.Ping() ? HealthCheckResponse.Types.ServingStatus.Serving : HealthCheckResponse.Types.ServingStatus.NotServing 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /src/cartservice/README.md: -------------------------------------------------------------------------------- 1 | # cartservice 2 | 3 | ## Configuration Environment Variables 4 | 5 | These environment variables control the behavior of the application. 6 | They make the application exercise different features available in the Splunk Observability. 7 | 8 | ### Synthetic External DB Access 9 | 10 | These environment variables are used to simulate access to an external DB for some of the requests. 11 | The access to DB is wrapped by OpenTelemetry spans and the backend infers an external DB from these spans. 12 | 13 | - `EXTERNAL_DB_ACCESS_RATE`: float [0, 1], percentage of redis spans that will be turned into external database spans 14 | - `EXTERNAL_DB_ERROR_RATE`: float [0, 1], percentage of external DB access spans that will report error 15 | - `EXTERNAL_DB_MAX_DURATION_MILLIS`: int, artificial maximum delay randomly added to external database spans (mock value) 16 | - `EXTERNAL_DB_NAME`: string, name of external database 17 | 18 | ### Optimizations 19 | 20 | These environment variables are used to trigger behaviors on the application that affect its performance characteristics. 21 | Each optimization affects latency, CPU, and memory in different ways. 22 | Keep that in mind when interpreting their effect. 23 | All these environment variables are boolean. 24 | 25 | - `FIX_EXCESSIVE_ALLOCATION`: bool, controls if the excessive allocation per request should be happening or not 26 | - `FIX_SLOW_LEAK`: bool, controls if a slowly growing memory leak is going to be happening or not 27 | - `OPTIMIZE_CPU`: bool, controls if the CPU is going to be efficiently used during input validation 28 | - `OPTIMIZE_BLOCKING`: bool, controls if the code is going to do some blocking to retrieve a handle to the Redis DB 29 | 30 | ## Building and Testing Locally 31 | 32 | Use the `dotnet` tool to build the project locally: 33 | 34 | ```bash 35 | dotnet build 36 | ``` 37 | 38 | After source changes are done, build the Docker image and validate it using `docker-compose` 39 | and the test project: 40 | 41 | ```bash 42 | pushd ./tests/ 43 | docker build -t cartservice ./.. 44 | docker-compose up -d 45 | dotnet test . 46 | docker-compose down 47 | popd 48 | ``` 49 | 50 | If you want to send data to the backend set the following environment below when starting the `docker-compose`: 51 | 52 | - `SPLUNK_ACCESS_TOKEN` 53 | - `OTEL_RESOURCE_ATTRIBUTES=deployment.environment=` 54 | -------------------------------------------------------------------------------- /src/cartservice/cartservice.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 12.0 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Always 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/cartservice/cartstore/Bogo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace cartservice.cartstore; 5 | 6 | public static class Bogo 7 | { 8 | public static void Sort(List list) 9 | { 10 | do 11 | { 12 | Shuffle(list); 13 | } while (!IsSorted(list)); 14 | } 15 | 16 | private static void Shuffle(List list) 17 | { 18 | var r = Random.Shared; 19 | for (int i = 0; i < list.Count; i++) 20 | { 21 | var k = r.Next(i, list.Count); 22 | var tmp = list[k]; 23 | list[k] = list[i]; 24 | list[i] = tmp; 25 | } 26 | } 27 | 28 | private static bool IsSorted(List list) 29 | { 30 | if (list is null || list.Count <= 1) 31 | { 32 | return true; 33 | } 34 | 35 | var prevChar = list[0]; 36 | for (int i = 1; i < list.Count; i++) 37 | { 38 | var currChar = list[i]; 39 | if (prevChar > currChar) 40 | { 41 | return false; 42 | } 43 | 44 | prevChar = currChar; 45 | } 46 | 47 | return true; 48 | } 49 | } -------------------------------------------------------------------------------- /src/cartservice/cartstore/ConfigHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace cartservice.cartstore; 4 | 5 | internal static class ConfigHelper 6 | { 7 | public static bool GetBoolEnvVar(string envVarName, bool defaultValue) 8 | { 9 | var varValue = Environment.GetEnvironmentVariable(envVarName) ?? string.Empty; 10 | var result = varValue.ToLowerInvariant() switch 11 | { 12 | "true" or "1" or "yes" => true, 13 | "false" or "0" or "no" => false, 14 | _ => defaultValue 15 | }; 16 | 17 | Console.WriteLine($"{nameof(ConfigHelper)}: env var {envVarName} = {result}"); 18 | return result; 19 | } 20 | } -------------------------------------------------------------------------------- /src/cartservice/cartstore/DatabaseCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using StackExchange.Redis; 5 | 6 | namespace cartservice.cartstore; 7 | 8 | public class DatabaseCache 9 | { 10 | private static readonly bool OptimizeBlocking = ConfigHelper.GetBoolEnvVar("OPTIMIZE_BLOCKING", defaultValue: true); 11 | 12 | private readonly ConnectionMultiplexer _conn; 13 | private readonly Semaphore _pool; 14 | 15 | public DatabaseCache(ConnectionMultiplexer connection) 16 | { 17 | _conn = connection; 18 | 19 | var maxConcurrentDBRetrieval = OptimizeBlocking ? Environment.ProcessorCount : 1; 20 | _pool = new Semaphore(0, maxConcurrentDBRetrieval); 21 | _pool.Release(maxConcurrentDBRetrieval); 22 | } 23 | 24 | public IDatabase ByPassBlocking() => _conn.GetDatabase(); 25 | 26 | public IDatabase Get() 27 | { 28 | _pool.WaitOne(); 29 | if (OptimizeBlocking) 30 | { 31 | _pool.Release(1); 32 | } 33 | else 34 | { 35 | Task.Run(async () => 36 | { 37 | await Task.Delay(Random.Shared.Next(250, 750)); 38 | _pool.Release(1); 39 | }); 40 | } 41 | 42 | return _conn.GetDatabase(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/cartservice/cartstore/LocalCartStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Concurrent; 17 | using System.Threading.Tasks; 18 | using System.Linq; 19 | using cartservice.interfaces; 20 | using Hipstershop; 21 | 22 | namespace cartservice.cartstore; 23 | 24 | internal class LocalCartStore : ICartStore 25 | { 26 | // Maps between user and their cart 27 | private ConcurrentDictionary userCartItems = new ConcurrentDictionary(); 28 | private readonly Hipstershop.Cart emptyCart = new Hipstershop.Cart(); 29 | 30 | public Task InitializeAsync() 31 | { 32 | Console.WriteLine("Local Cart Store was initialized"); 33 | 34 | return Task.CompletedTask; 35 | } 36 | 37 | public Task AddItemAsync(string userId, string productId, int quantity) 38 | { 39 | Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}"); 40 | var newCart = new Hipstershop.Cart 41 | { 42 | UserId = userId, 43 | Items = { new Hipstershop.CartItem { ProductId = productId, Quantity = quantity } } 44 | }; 45 | userCartItems.AddOrUpdate(userId, newCart, 46 | (k, exVal) => 47 | { 48 | // If the item exists, we update its quantity 49 | var existingItem = exVal.Items.SingleOrDefault(item => item.ProductId == productId); 50 | if (existingItem != null) 51 | { 52 | existingItem.Quantity += quantity; 53 | } 54 | else 55 | { 56 | exVal.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); 57 | } 58 | 59 | return exVal; 60 | }); 61 | 62 | return Task.CompletedTask; 63 | } 64 | 65 | public Task EmptyCartAsync(string userId) 66 | { 67 | Console.WriteLine($"EmptyCartAsync called with userId={userId}"); 68 | userCartItems[userId] = new Hipstershop.Cart(); 69 | 70 | return Task.CompletedTask; 71 | } 72 | 73 | public Task GetCartAsync(string userId) 74 | { 75 | Console.WriteLine($"GetCartAsync called with userId={userId}"); 76 | Hipstershop.Cart cart = null; 77 | if (!userCartItems.TryGetValue(userId, out cart)) 78 | { 79 | Console.WriteLine($"No carts for user {userId}"); 80 | return Task.FromResult(emptyCart); 81 | } 82 | 83 | return Task.FromResult(cart); 84 | } 85 | 86 | public bool Ping() 87 | { 88 | return true; 89 | } 90 | } -------------------------------------------------------------------------------- /src/cartservice/cartstore/UserId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | 5 | namespace cartservice.cartstore; 6 | 7 | public static class UserId 8 | { 9 | private const int MaxCacheSize = 500; 10 | 11 | private readonly static bool FixExcessiveAllocation = ConfigHelper.GetBoolEnvVar("FIX_EXCESSIVE_ALLOCATION", defaultValue: true); 12 | private readonly static bool FixSlowLeak = ConfigHelper.GetBoolEnvVar("FIX_SLOW_LEAK", defaultValue: true); 13 | private readonly static bool OptimizeCPU = ConfigHelper.GetBoolEnvVar("OPTIMIZE_CPU", defaultValue: true); 14 | 15 | private readonly static object ValidationCacheLock = new object(); 16 | private readonly static ConcurrentDictionary ValidationCache = new ConcurrentDictionary(); 17 | private readonly static ConcurrentQueue UserIdQueue = new ConcurrentQueue(); 18 | private static ulong prevValidationCacheCount = 0; 19 | 20 | public static bool IsValid(string userId) 21 | { 22 | bool? result; 23 | if (TryCachedValidation(userId, out result)) 24 | { 25 | return result ?? false; 26 | } 27 | 28 | if (OptimizeCPU) 29 | { 30 | result = Guid.TryParse(userId, out _); 31 | CacheValidation(userId, result.Value); 32 | return result ?? false; 33 | } 34 | 35 | // 36 | // Silly user id validation 37 | // 38 | 39 | var idChars = new List(userId.Length); 40 | var tmpChars = new List(userId.Length); 41 | foreach (var c in userId) 42 | { 43 | if (c == '-') 44 | { 45 | continue; 46 | } 47 | 48 | tmpChars.Add(c); 49 | const int idealSizeForBogoSort = 10; // Not too fast, not too slow. 50 | if (tmpChars.Count == idealSizeForBogoSort) 51 | { 52 | Bogo.Sort(tmpChars); 53 | idChars.AddRange(tmpChars); 54 | tmpChars.Clear(); 55 | } 56 | } 57 | 58 | Bogo.Sort(tmpChars); 59 | idChars.AddRange(tmpChars); 60 | tmpChars.Clear(); 61 | 62 | result = true; 63 | foreach (var c in idChars) 64 | { 65 | if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) 66 | { 67 | continue; 68 | } 69 | 70 | result = false; 71 | break; 72 | } 73 | 74 | CacheValidation(userId, result ?? false); 75 | return result ?? false; 76 | } 77 | 78 | private static bool TryCachedValidation(string userId, out bool? result) 79 | { 80 | var localUserId = ProcessUserId(userId); 81 | return ValidationCache.TryGetValue(localUserId, out result); 82 | } 83 | 84 | private static void CacheValidation(string userId, bool result) 85 | { 86 | lock(ValidationCacheLock) 87 | { 88 | if (ValidationCache.TryAdd(userId, result)) 89 | { 90 | UserIdQueue.Enqueue(userId); 91 | } 92 | } 93 | 94 | string expirationCandidate; 95 | if (FixSlowLeak && ValidationCache.Count >= MaxCacheSize && UserIdQueue.TryDequeue(out expirationCandidate)) 96 | { 97 | ValidationCache.Remove(expirationCandidate, out var _); 98 | return; 99 | } 100 | 101 | if ((ulong)ValidationCache.Count - prevValidationCacheCount > MaxCacheSize) 102 | { 103 | var numOfItemsToEvictFromCache = (MaxCacheSize/100)*2; 104 | for (int i = 0; i < numOfItemsToEvictFromCache; i++) 105 | { 106 | if (UserIdQueue.TryDequeue(out expirationCandidate)) 107 | { 108 | ValidationCache.Remove(expirationCandidate, out var _); 109 | } 110 | } 111 | 112 | prevValidationCacheCount = (ulong)ValidationCache.Count + MaxCacheSize; 113 | } 114 | } 115 | 116 | private static string ProcessUserId(string userId) 117 | { 118 | var processedUserId = string.Empty; 119 | if (FixExcessiveAllocation) 120 | { 121 | processedUserId = userId; 122 | } 123 | else 124 | { 125 | foreach (var c in userId) 126 | { 127 | processedUserId += c; 128 | } 129 | } 130 | 131 | return processedUserId; 132 | } 133 | } -------------------------------------------------------------------------------- /src/cartservice/genproto.bat: -------------------------------------------------------------------------------- 1 | @REM Copyright 2018 Google LLC 2 | @REM 3 | @REM Licensed under the Apache License, Version 2.0 (the "License"); 4 | @REM you may not use this file except in compliance with the License. 5 | @REM You may obtain a copy of the License at 6 | @REM 7 | @REM http://www.apache.org/licenses/LICENSE-2.0 8 | @REM 9 | @REM Unless required by applicable law or agreed to in writing, software 10 | @REM distributed under the License is distributed on an "AS IS" BASIS, 11 | @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @REM See the License for the specific language governing permissions and 13 | @REM limitations under the License. 14 | 15 | @rem Generate the C# code for .proto files 16 | 17 | setlocal 18 | 19 | @rem enter this directory 20 | cd /d %~dp0 21 | 22 | set NUGET_PATH=%UserProfile%\.nuget\packages 23 | set TOOLS_PATH=%NUGET_PATH%\Grpc.Tools\2.54.0\tools\windows_x64 24 | 25 | %TOOLS_PATH%\protoc.exe -I%~dp0/../../pb;%NUGET_PATH%\google.protobuf.tools\3.23.1\tools\ --csharp_out %~dp0\grpc_generated %~dp0\..\..\pb\demo.proto --grpc_out %~dp0\grpc_generated --plugin=protoc-gen-grpc=%TOOLS_PATH%\grpc_csharp_plugin.exe 26 | 27 | endlocal 28 | -------------------------------------------------------------------------------- /src/cartservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Generate the C# code for .proto files 18 | set -e 19 | 20 | PROTODIR=../../pb 21 | 22 | # enter this directory 23 | CWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 24 | 25 | protoc --csharp_out=$CWD/grpc_generated -I $PROTODIR $PROTODIR/demo.proto 26 | 27 | -------------------------------------------------------------------------------- /src/cartservice/interfaces/ICartStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Threading.Tasks; 16 | 17 | namespace cartservice.interfaces; 18 | 19 | internal interface ICartStore 20 | { 21 | Task InitializeAsync(); 22 | 23 | Task AddItemAsync(string userId, string productId, int quantity); 24 | Task EmptyCartAsync(string userId); 25 | 26 | Task GetCartAsync(string userId); 27 | 28 | bool Ping(); 29 | } -------------------------------------------------------------------------------- /src/cartservice/start: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | ./cartservice start -------------------------------------------------------------------------------- /src/cartservice/tests/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /obj/* 3 | /.vs/* -------------------------------------------------------------------------------- /src/cartservice/tests/cartservice.tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Always 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/cartservice/tests/cartservice.tests.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /src/cartservice/tests/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | 4 | redis: 5 | image: redis:alpine 6 | 7 | cartservice: 8 | image: cartservice 9 | environment: 10 | - REDIS_ADDR=redis:6379 11 | - LISTEN_ADDR=0.0.0.0 12 | - PORT=7070 13 | - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 14 | - OTEL_RESOURCE_ATTRIBUTES=deployment.environment= 15 | - OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES=Cartservice 16 | - EXTERNAL_DB_NAME=Galactus.Postgres 17 | - EXTERNAL_DB_ACCESS_RATE=0.75 18 | - EXTERNAL_DB_MAX_DURATION_MILLIS=750 19 | - EXTERNAL_DB_ERROR_RATE=0.1 20 | - FIX_EXCESSIVE_ALLOCATION=false 21 | - FIX_SLOW_LEAK=false 22 | - OPTIMIZE_CPU=false 23 | - OPTIMIZE_BLOCKING=false 24 | ports: 25 | - 7070:7070 26 | depends_on: 27 | - redis 28 | - otel-collector 29 | 30 | otel-collector: 31 | image: otel/opentelemetry-collector-contrib:0.78.0 32 | volumes: 33 | - ./otel-config.yaml:/etc/otel/config.yaml 34 | command: --config /etc/otel/config.yaml 35 | environment: 36 | - SPLUNK_ACCESS_TOKEN 37 | - SPLUNK_REALM=us0 38 | ports: 39 | - "1777:1777" # pprof extension 40 | - "8888:8888" # Prometheus metrics exposed by the collector 41 | - "8889:8889" # Prometheus exporter metrics 42 | - "13133:13133" # health_check extension 43 | - "4317:4317" # OTLP gRPC receiver 44 | - "4318:4318" # OTLP HTTP receiver 45 | - "55679:55679" # zpages extension 46 | - "9411:9411" # zipkin receiver 47 | - "9943:9943" # signalfx receiver 48 | -------------------------------------------------------------------------------- /src/cartservice/tests/otel-config.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | health_check: 3 | pprof: 4 | endpoint: :1777 5 | zpages: 6 | endpoint: :55679 7 | 8 | receivers: 9 | otlp: 10 | protocols: 11 | grpc: 12 | http: 13 | signalfx: 14 | zipkin: 15 | 16 | processors: 17 | batch: 18 | 19 | exporters: 20 | logging: 21 | verbosity: detailed 22 | sapm: 23 | access_token: "${SPLUNK_ACCESS_TOKEN}" 24 | endpoint: "https://ingest.${SPLUNK_REALM}.signalfx.com/v2/trace" 25 | splunk_hec: 26 | token: "${SPLUNK_ACCESS_TOKEN}" 27 | endpoint: "https://ingest.${SPLUNK_REALM}.signalfx.com/v1/log" 28 | signalfx: 29 | access_token: "${SPLUNK_ACCESS_TOKEN}" 30 | realm: "${SPLUNK_REALM}" 31 | prometheus: 32 | endpoint: "0.0.0.0:8889" 33 | namespace: promexample 34 | const_labels: 35 | label1: value1 36 | 37 | service: 38 | pipelines: 39 | traces: 40 | receivers: [otlp] 41 | processors: [batch] 42 | exporters: [logging, sapm] 43 | metrics: 44 | receivers: [otlp] 45 | processors: [batch] 46 | exporters: [logging, signalfx] 47 | logs: 48 | receivers: [otlp] 49 | processors: [batch] 50 | exporters: [logging, splunk_hec] 51 | 52 | extensions: [health_check, pprof, zpages] 53 | -------------------------------------------------------------------------------- /src/checkoutservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | RUN apk add --no-cache ca-certificates git 3 | 4 | WORKDIR /app 5 | COPY . . 6 | RUN go build -o /go/bin/checkoutservice . 7 | 8 | FROM alpine AS release 9 | RUN apk add --no-cache ca-certificates 10 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 11 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 12 | chmod +x /bin/grpc_health_probe 13 | COPY --from=builder /go/bin/checkoutservice /checkoutservice 14 | EXPOSE 5050 15 | ENTRYPOINT ["/checkoutservice"] 16 | -------------------------------------------------------------------------------- /src/checkoutservice/README.md: -------------------------------------------------------------------------------- 1 | # checkoutservice 2 | 3 | 4 | ## Environment Variables 5 | 6 | `MAX_RETRY_ATTEMPTS`: int, Nax number of retries for payment service before returning error 7 | `RETRY_INITIAL_SLEEP_MILLIS`: int, Initial sleep time for retry, value doubles every retry 8 | -------------------------------------------------------------------------------- /src/checkoutservice/examples/placeorder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using: https://github.com/njpatel/grpcc 3 | * src/checkoutservice $ grpcc --insecure -p proto/demo.proto -a localhost:5050 -s hipstershop.CheckoutService --exec examples/placeorder.js 4 | */ 5 | client.placeOrder( 6 | { 7 | user_id: '123', 8 | user_currency: 'USD', 9 | address: { 10 | street_address: '123 foo bar lane', 11 | city: 'san francisco', 12 | state: 'CA', 13 | country: 'USA', 14 | zip_code: 94329, 15 | }, 16 | email: 'foo@bar.com', 17 | credit_card: { 18 | credit_card_number: '4009366231016609', 19 | credit_card_cvv: 123, 20 | credit_card_expiration_year: 2025, 21 | credit_card_expiration_month: 12, 22 | }, 23 | }, 24 | printReply 25 | ); 26 | -------------------------------------------------------------------------------- /src/checkoutservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$(go env GOPATH)/bin 20 | protodir=../../pb 21 | outdir=./genproto 22 | 23 | protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto 24 | -------------------------------------------------------------------------------- /src/checkoutservice/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/signalfx/microservices-demo/src/checkoutservice 2 | 3 | go 1.22 4 | 5 | require ( 6 | cloud.google.com/go/profiler v0.4.1 7 | github.com/google/uuid v1.6.0 8 | github.com/opentracing/opentracing-go v1.2.0 9 | github.com/signalfx/signalfx-go-tracing v1.12.0 10 | github.com/signalfx/signalfx-go-tracing/contrib/google.golang.org/grpc v1.12.0 11 | github.com/sirupsen/logrus v1.9.3 12 | google.golang.org/grpc v1.66.0 13 | google.golang.org/protobuf v1.34.2 14 | ) 15 | 16 | require ( 17 | cloud.google.com/go v0.115.0 // indirect 18 | cloud.google.com/go/auth v0.6.0 // indirect 19 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 20 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 21 | github.com/go-logfmt/logfmt v0.5.0 // indirect 22 | github.com/go-logr/logr v1.4.1 // indirect 23 | github.com/go-logr/stdr v1.2.2 // indirect 24 | github.com/go-stack/stack v1.8.0 // indirect 25 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 26 | github.com/golang/protobuf v1.5.4 // indirect 27 | github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect 28 | github.com/google/s2a-go v0.1.7 // indirect 29 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 30 | github.com/googleapis/gax-go/v2 v2.12.5 // indirect 31 | github.com/josharian/intern v1.0.0 // indirect 32 | github.com/mailru/easyjson v0.7.7 // indirect 33 | github.com/philhofer/fwd v1.1.1 // indirect 34 | github.com/signalfx/golib v2.5.1+incompatible // indirect 35 | github.com/tinylib/msgp v1.1.6 // indirect 36 | go.opencensus.io v0.24.0 // indirect 37 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 38 | go.opentelemetry.io/otel v1.24.0 // indirect 39 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 40 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 41 | golang.org/x/crypto v0.26.0 // indirect 42 | golang.org/x/net v0.28.0 // indirect 43 | golang.org/x/oauth2 v0.21.0 // indirect 44 | golang.org/x/sync v0.8.0 // indirect 45 | golang.org/x/sys v0.23.0 // indirect 46 | golang.org/x/text v0.17.0 // indirect 47 | golang.org/x/time v0.5.0 // indirect 48 | google.golang.org/api v0.186.0 // indirect 49 | google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect 50 | google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect 51 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /src/currencyservice/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /src/currencyservice/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /src/currencyservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine as base 2 | 3 | FROM base as builder 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install --only=production 10 | 11 | FROM base 12 | 13 | RUN rm -r /usr/local/lib/node_modules 14 | 15 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 16 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 17 | chmod +x /bin/grpc_health_probe 18 | 19 | WORKDIR /usr/src/app 20 | 21 | COPY --from=builder /usr/src/app/node_modules ./node_modules 22 | 23 | COPY . . 24 | 25 | EXPOSE 7000 26 | 27 | ENTRYPOINT [ "node", "-r", "./tracing.js", "server.js" ] 28 | -------------------------------------------------------------------------------- /src/currencyservice/data/currency_conversion.json: -------------------------------------------------------------------------------- 1 | { 2 | "EUR": "1.0", 3 | "USD": "1.1305", 4 | "JPY": "126.40", 5 | "BGN": "1.9558", 6 | "CZK": "25.592", 7 | "DKK": "7.4609", 8 | "GBP": "0.85970", 9 | "HUF": "315.51", 10 | "PLN": "4.2996", 11 | "RON": "4.7463", 12 | "SEK": "10.5375", 13 | "CHF": "1.1360", 14 | "ISK": "136.80", 15 | "NOK": "9.8040", 16 | "HRK": "7.4210", 17 | "RUB": "74.4208", 18 | "TRY": "6.1247", 19 | "AUD": "1.6072", 20 | "BRL": "4.2682", 21 | "CAD": "1.5128", 22 | "CNY": "7.5857", 23 | "HKD": "8.8743", 24 | "IDR": "15999.40", 25 | "ILS": "4.0875", 26 | "INR": "79.4320", 27 | "KRW": "1275.05", 28 | "MXN": "21.7999", 29 | "MYR": "4.6289", 30 | "NZD": "1.6679", 31 | "PHP": "59.083", 32 | "SGD": "1.5349", 33 | "THB": "36.012", 34 | "ZAR": "16.0583" 35 | } -------------------------------------------------------------------------------- /src/currencyservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # protos are loaded dynamically for node, simply copies over the proto. 18 | mkdir -p proto 19 | cp -r ../../pb/* ./proto 20 | -------------------------------------------------------------------------------- /src/currencyservice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grpc-currency-service", 3 | "version": "0.1.0", 4 | "description": "A gRPC currency conversion microservice", 5 | "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "semistandard *.js" 9 | }, 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "@grpc/grpc-js": "^1.8.4", 13 | "@grpc/proto-loader": "^0.7.4", 14 | "@opentelemetry/api": "~0.18.1", 15 | "@opentelemetry/core": "~0.18.1", 16 | "@opentelemetry/exporter-zipkin": "~0.18.1", 17 | "@opentelemetry/instrumentation-grpc": "~0.18.1", 18 | "@opentelemetry/node": "~0.18.1", 19 | "@opentelemetry/propagator-b3": "~0.18.1", 20 | "@opentelemetry/tracing": "~0.18.1", 21 | "minimatch": "^3.0.5", 22 | "pino": "^5.17.0" 23 | }, 24 | "devDependencies": { 25 | "semistandard": "^16.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/currencyservice/proto/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /src/currencyservice/tracing.js: -------------------------------------------------------------------------------- 1 | const { NodeTracerProvider } = require('@opentelemetry/node'); 2 | const { B3MultiPropagator } = require('@opentelemetry/propagator-b3'); 3 | const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); 4 | const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); 5 | const { BatchSpanProcessor, ConsoleSpanExporter } = require('@opentelemetry/tracing'); 6 | const { registerInstrumentations } = require('@opentelemetry/instrumentation'); 7 | 8 | const provider = new NodeTracerProvider(); 9 | provider.register({ 10 | propagator: new B3MultiPropagator(), 11 | }); 12 | 13 | registerInstrumentations({ 14 | instrumentations: [ 15 | new GrpcInstrumentation(), 16 | ], 17 | }); 18 | 19 | const exporter = new ZipkinExporter({ 20 | serviceName: 'currencyservice', 21 | url: process.env.SIGNALFX_ENDPOINT_URL, 22 | }); 23 | 24 | provider.addSpanProcessor(new BatchSpanProcessor(exporter)); 25 | 26 | -------------------------------------------------------------------------------- /src/emailservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-alpine3.18 2 | 3 | RUN apk add --no-cache \ 4 | g++ \ 5 | musl-dev \ 6 | libffi-dev 7 | 8 | # get packages 9 | COPY requirements.txt . 10 | RUN pip install --no-cache-dir -r requirements.txt 11 | 12 | # Enable unbuffered logging 13 | ENV PYTHONUNBUFFERED=1 14 | 15 | # Download the grpc health probe 16 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 17 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 18 | chmod +x /bin/grpc_health_probe 19 | 20 | WORKDIR /email_server 21 | 22 | # Add the application 23 | COPY . . 24 | 25 | EXPOSE 8080 26 | ENTRYPOINT [ "python", "email_server.py" ] 27 | -------------------------------------------------------------------------------- /src/emailservice/email_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import grpc 18 | 19 | import demo_pb2 20 | import demo_pb2_grpc 21 | 22 | from logger import getJSONLogger 23 | logger = getJSONLogger('emailservice-client') 24 | 25 | from opencensus.trace.tracer import Tracer 26 | from opencensus.trace.exporters import stackdriver_exporter 27 | from opencensus.trace.ext.grpc import client_interceptor 28 | 29 | try: 30 | exporter = stackdriver_exporter.StackdriverExporter() 31 | tracer = Tracer(exporter=exporter) 32 | tracer_interceptor = client_interceptor.OpenCensusClientInterceptor(tracer, host_port='0.0.0.0:8080') 33 | except: 34 | tracer_interceptor = client_interceptor.OpenCensusClientInterceptor() 35 | 36 | def send_confirmation_email(email, order): 37 | channel = grpc.insecure_channel('0.0.0.0:8080') 38 | channel = grpc.intercept_channel(channel, tracer_interceptor) 39 | stub = demo_pb2_grpc.EmailServiceStub(channel) 40 | try: 41 | response = stub.SendOrderConfirmation(demo_pb2.SendOrderConfirmationRequest( 42 | email = email, 43 | order = order 44 | )) 45 | logger.info('Request sent.') 46 | except grpc.RpcError as err: 47 | logger.error(err.details()) 48 | logger.error('{}, {}'.format(err.code().name, err.code().value)) 49 | 50 | if __name__ == '__main__': 51 | logger.info('Client for email service.') 52 | -------------------------------------------------------------------------------- /src/emailservice/email_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from concurrent import futures 18 | import argparse 19 | import os 20 | import sys 21 | import time 22 | import grpc 23 | import urllib.parse 24 | from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateError 25 | 26 | import demo_pb2 27 | import demo_pb2_grpc 28 | from grpc_health.v1 import health_pb2 29 | from grpc_health.v1 import health_pb2_grpc 30 | 31 | from opentelemetry import trace 32 | from opentelemetry.propagate import set_global_textmap 33 | from opentelemetry.sdk.trace import TracerProvider 34 | from opentelemetry.exporter.zipkin.json import ZipkinExporter 35 | from opentelemetry.sdk.trace.export import BatchSpanProcessor 36 | from opentelemetry.propagators.b3 import B3MultiFormat 37 | from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer 38 | from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient 39 | 40 | from logger import getJSONLogger 41 | logger = getJSONLogger('emailservice-server') 42 | 43 | zipkin_exporter = ZipkinExporter(endpoint=os.environ['SIGNALFX_ENDPOINT_URL']) 44 | span_processor = BatchSpanProcessor(zipkin_exporter) 45 | 46 | set_global_textmap(B3MultiFormat()) 47 | trace.set_tracer_provider(TracerProvider()) 48 | trace.get_tracer_provider().add_span_processor(span_processor) 49 | tracer = trace.get_tracer(__name__) 50 | 51 | instrumentor = GrpcInstrumentorClient() 52 | instrumentor.instrument() 53 | grpc_server_instrumentor = GrpcInstrumentorServer() 54 | grpc_server_instrumentor.instrument() 55 | 56 | 57 | # Loads confirmation email template from file 58 | env = Environment( 59 | loader=FileSystemLoader('templates'), 60 | autoescape=select_autoescape(['html', 'xml']) 61 | ) 62 | template = env.get_template('confirmation.html') 63 | 64 | class BaseEmailService(demo_pb2_grpc.EmailServiceServicer): 65 | def Check(self, request, context): 66 | return health_pb2.HealthCheckResponse( 67 | status=health_pb2.HealthCheckResponse.SERVING) 68 | 69 | def Watch(self, request, context, send_response_callback=None): 70 | context.write(health_pb2.HealthCheckResponse(status=health_pb2.HealthCheckResponse.SERVING)) 71 | 72 | 73 | class DummyEmailService(BaseEmailService): 74 | def SendOrderConfirmation(self, request, context): 75 | logger.info('A request to send order confirmation email to {} has been received.'.format(request.email)) 76 | return demo_pb2.Empty() 77 | 78 | 79 | class HealthCheck(): 80 | def Check(self, request, context): 81 | return health_pb2.HealthCheckResponse( 82 | status=health_pb2.HealthCheckResponse.SERVING) 83 | 84 | def Watch(self, request, context, send_response_callback=None): 85 | context.write(health_pb2.HealthCheckResponse(status=health_pb2.HealthCheckResponse.SERVING)) 86 | 87 | def start(): 88 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 89 | service = None 90 | service = DummyEmailService() 91 | 92 | demo_pb2_grpc.add_EmailServiceServicer_to_server(service, server) 93 | health_pb2_grpc.add_HealthServicer_to_server(service, server) 94 | 95 | port = os.environ.get('PORT', "8080") 96 | logger.info("listening on port: "+port) 97 | server.add_insecure_port('[::]:'+port) 98 | server.start() 99 | try: 100 | while True: 101 | time.sleep(3600) 102 | except KeyboardInterrupt: 103 | server.stop(0) 104 | 105 | 106 | if __name__ == '__main__': 107 | logger.info('starting the email service in dummy mode.') 108 | 109 | start() 110 | -------------------------------------------------------------------------------- /src/emailservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | python -m grpc_tools.protoc -I../../pb --python_out=. --grpc_python_out=. ../../pb/demo.proto 20 | -------------------------------------------------------------------------------- /src/emailservice/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import logging 18 | import sys 19 | from pythonjsonlogger import jsonlogger 20 | 21 | # TODO(yoshifumi) this class is duplicated since other Python services are 22 | # not sharing the modules for logging. 23 | class CustomJsonFormatter(jsonlogger.JsonFormatter): 24 | def add_fields(self, log_record, record, message_dict): 25 | super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) 26 | if not log_record.get('timestamp'): 27 | log_record['timestamp'] = record.created 28 | if log_record.get('severity'): 29 | log_record['severity'] = log_record['severity'].upper() 30 | else: 31 | log_record['severity'] = record.levelname 32 | 33 | def getJSONLogger(name): 34 | logger = logging.getLogger(name) 35 | handler = logging.StreamHandler(sys.stdout) 36 | formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s') 37 | handler.setFormatter(formatter) 38 | logger.addHandler(handler) 39 | logger.setLevel(logging.INFO) 40 | logger.propagate = False 41 | return logger 42 | -------------------------------------------------------------------------------- /src/emailservice/requirements.in: -------------------------------------------------------------------------------- 1 | jinja2==3.0.1 2 | grpcio-health-checking==1.47.0 3 | grpcio==1.47.0 4 | opentelemetry-api==1.20.0 5 | opentelemetry-propagator-b3==1.20.0 6 | opentelemetry-instrumentation==0.41b0 7 | opentelemetry-sdk==1.20.0 8 | opentelemetry-exporter-zipkin-json==1.20.0 9 | opentelemetry-instrumentation-grpc==0.41b0 10 | opentelemetry-instrumentation-requests==0.41b0 11 | opentelemetry-instrumentation-sqlite3==0.41b0 12 | python-json-logger==0.1.11 13 | requests==2.31.0 14 | urllib3==1.26.6 15 | -------------------------------------------------------------------------------- /src/emailservice/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.9 3 | # by the following command: 4 | # 5 | # pip-compile --output-file=requirements.txt 6 | # 7 | certifi==2023.7.22 8 | # via requests 9 | charset-normalizer==3.2.0 10 | # via requests 11 | deprecated==1.2.14 12 | # via 13 | # opentelemetry-api 14 | # opentelemetry-propagator-b3 15 | grpcio==1.47.0 16 | # via 17 | # -r requirements.in 18 | # grpcio-health-checking 19 | grpcio-health-checking==1.47.0 20 | # via -r requirements.in 21 | idna==3.4 22 | # via requests 23 | importlib-metadata==6.8.0 24 | # via opentelemetry-api 25 | jinja2==3.0.1 26 | # via -r requirements.in 27 | markupsafe==2.1.3 28 | # via jinja2 29 | opentelemetry-api==1.20.0 30 | # via 31 | # -r requirements.in 32 | # opentelemetry-exporter-zipkin-json 33 | # opentelemetry-exporter-zipkin-proto-http 34 | # opentelemetry-instrumentation 35 | # opentelemetry-instrumentation-dbapi 36 | # opentelemetry-instrumentation-grpc 37 | # opentelemetry-instrumentation-requests 38 | # opentelemetry-instrumentation-sqlite3 39 | # opentelemetry-propagator-b3 40 | # opentelemetry-sdk 41 | opentelemetry-exporter-zipkin==1.20.0 42 | # via -r requirements.in 43 | opentelemetry-exporter-zipkin-json==1.20.0 44 | # via 45 | # opentelemetry-exporter-zipkin 46 | # opentelemetry-exporter-zipkin-proto-http 47 | opentelemetry-exporter-zipkin-proto-http==1.20.0 48 | # via opentelemetry-exporter-zipkin 49 | opentelemetry-instrumentation==0.41b0 50 | # via 51 | # -r requirements.in 52 | # opentelemetry-instrumentation-dbapi 53 | # opentelemetry-instrumentation-grpc 54 | # opentelemetry-instrumentation-requests 55 | # opentelemetry-instrumentation-sqlite3 56 | opentelemetry-instrumentation-dbapi==0.41b0 57 | # via opentelemetry-instrumentation-sqlite3 58 | opentelemetry-instrumentation-grpc==0.41b0 59 | # via -r requirements.in 60 | opentelemetry-instrumentation-requests==0.41b0 61 | # via -r requirements.in 62 | opentelemetry-instrumentation-sqlite3==0.41b0 63 | # via -r requirements.in 64 | opentelemetry-propagator-b3==1.20.0 65 | # via -r requirements.in 66 | opentelemetry-sdk==1.20.0 67 | # via 68 | # -r requirements.in 69 | # opentelemetry-exporter-zipkin-json 70 | # opentelemetry-exporter-zipkin-proto-http 71 | # opentelemetry-instrumentation-grpc 72 | opentelemetry-semantic-conventions==0.41b0 73 | # via 74 | # opentelemetry-instrumentation-dbapi 75 | # opentelemetry-instrumentation-grpc 76 | # opentelemetry-instrumentation-requests 77 | # opentelemetry-sdk 78 | opentelemetry-util-http==0.41b0 79 | # via opentelemetry-instrumentation-requests 80 | protobuf==3.20.3 81 | # via 82 | # grpcio-health-checking 83 | # opentelemetry-exporter-zipkin-proto-http 84 | python-json-logger==0.1.11 85 | # via -r requirements.in 86 | requests==2.31.0 87 | # via 88 | # -r requirements.in 89 | # opentelemetry-exporter-zipkin-json 90 | # opentelemetry-exporter-zipkin-proto-http 91 | six==1.16.0 92 | # via grpcio 93 | typing-extensions==4.8.0 94 | # via opentelemetry-sdk 95 | urllib3==1.26.6 96 | # via 97 | # -r requirements.in 98 | # requests 99 | wrapt==1.15.0 100 | # via 101 | # deprecated 102 | # opentelemetry-instrumentation 103 | # opentelemetry-instrumentation-dbapi 104 | # opentelemetry-instrumentation-grpc 105 | zipp==3.17.0 106 | # via importlib-metadata 107 | 108 | # The following packages are considered to be unsafe in a requirements file: 109 | # setuptools 110 | -------------------------------------------------------------------------------- /src/emailservice/templates/confirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your Order Confirmation 5 | 6 | 7 | 12 | 13 |

Your Order Confirmation

14 |

Thanks for shopping with us!

15 |

Order ID

16 |

#{{ order.order_id }}

17 |

Shipping

18 |

#{{ order.shipping_tracking_id }}

19 |

{{ order.shipping_cost.units }}. {{ "%02d" | format(order.shipping_cost.nanos // 10000000) }} {{ order.shipping_cost.currency_code }}

20 |

{{ order.shipping_address.street_address_1 }}, {{order.shipping_address.street_address_2}}, {{order.shipping_address.city}}, {{order.shipping_address.country}} {{order.shipping_address.zip_code}}

21 |

Items

22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for item in order.items %} 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 |
Item No.QuantityPrice
#{{ item.item.product_id }}{{ item.item.quantity }}{{ item.cost.units }}.{{ "%02d" | format(item.cost.nanos // 10000000) }} {{ item.cost.currency_code }}
36 | 37 | 38 | -------------------------------------------------------------------------------- /src/frontend/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/.gitkeep -------------------------------------------------------------------------------- /src/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | RUN apk add --no-cache ca-certificates git 3 | 4 | WORKDIR /app 5 | COPY . . 6 | RUN go build -o /go/bin/frontend . 7 | 8 | FROM alpine AS release 9 | RUN apk add --no-cache ca-certificates \ 10 | busybox-extras net-tools bind-tools 11 | WORKDIR /frontend 12 | COPY --from=builder /go/bin/frontend /frontend/server 13 | COPY ./templates ./templates 14 | COPY ./static ./static 15 | EXPOSE 8080 16 | ENTRYPOINT ["/frontend/server"] 17 | -------------------------------------------------------------------------------- /src/frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | -------------------------------------------------------------------------------- /src/frontend/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$(go env GOPATH)/bin 20 | protodir=../../pb 21 | outdir=./genproto 22 | 23 | protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto 24 | -------------------------------------------------------------------------------- /src/frontend/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/signalfx/microservices-demo/src/frontend 2 | 3 | go 1.22 4 | 5 | require ( 6 | cloud.google.com/go/profiler v0.4.1 7 | github.com/google/uuid v1.6.0 8 | github.com/gorilla/mux v1.8.1 9 | github.com/opentracing/opentracing-go v1.2.0 10 | github.com/pkg/errors v0.8.1 11 | github.com/signalfx/signalfx-go-tracing v1.12.0 12 | github.com/signalfx/signalfx-go-tracing/contrib/google.golang.org/grpc v1.12.0 13 | github.com/signalfx/signalfx-go-tracing/contrib/gorilla/mux v1.12.0 14 | github.com/sirupsen/logrus v1.9.3 15 | google.golang.org/grpc v1.66.0 16 | google.golang.org/protobuf v1.34.2 17 | ) 18 | 19 | require ( 20 | cloud.google.com/go v0.115.0 // indirect 21 | cloud.google.com/go/auth v0.6.0 // indirect 22 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 23 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.5.0 // indirect 25 | github.com/go-logr/logr v1.4.1 // indirect 26 | github.com/go-logr/stdr v1.2.2 // indirect 27 | github.com/go-stack/stack v1.8.0 // indirect 28 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 29 | github.com/golang/protobuf v1.5.4 // indirect 30 | github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect 31 | github.com/google/s2a-go v0.1.7 // indirect 32 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 33 | github.com/googleapis/gax-go/v2 v2.12.5 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/mailru/easyjson v0.7.7 // indirect 36 | github.com/philhofer/fwd v1.1.1 // indirect 37 | github.com/signalfx/golib v2.5.1+incompatible // indirect 38 | github.com/tinylib/msgp v1.1.6 // indirect 39 | go.opencensus.io v0.24.0 // indirect 40 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 41 | go.opentelemetry.io/otel v1.24.0 // indirect 42 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 43 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 44 | golang.org/x/crypto v0.26.0 // indirect 45 | golang.org/x/net v0.28.0 // indirect 46 | golang.org/x/oauth2 v0.21.0 // indirect 47 | golang.org/x/sync v0.8.0 // indirect 48 | golang.org/x/sys v0.23.0 // indirect 49 | golang.org/x/text v0.17.0 // indirect 50 | golang.org/x/time v0.5.0 // indirect 51 | google.golang.org/api v0.186.0 // indirect 52 | google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect 53 | google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect 54 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /src/frontend/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | "time" 21 | 22 | "github.com/google/uuid" 23 | "github.com/sirupsen/logrus" 24 | ) 25 | 26 | type ctxKeyLog struct{} 27 | type ctxKeyRequestID struct{} 28 | 29 | type logHandler struct { 30 | log *logrus.Logger 31 | next http.Handler 32 | } 33 | 34 | type responseRecorder struct { 35 | b int 36 | status int 37 | w http.ResponseWriter 38 | } 39 | 40 | func (r *responseRecorder) Header() http.Header { return r.w.Header() } 41 | 42 | func (r *responseRecorder) Write(p []byte) (int, error) { 43 | if r.status == 0 { 44 | r.status = http.StatusOK 45 | } 46 | n, err := r.w.Write(p) 47 | r.b += n 48 | return n, err 49 | } 50 | 51 | func (r *responseRecorder) WriteHeader(statusCode int) { 52 | r.status = statusCode 53 | r.w.WriteHeader(statusCode) 54 | } 55 | 56 | func (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 57 | ctx := r.Context() 58 | requestID, _ := uuid.NewRandom() 59 | ctx = context.WithValue(ctx, ctxKeyRequestID{}, requestID.String()) 60 | 61 | start := time.Now() 62 | rr := &responseRecorder{w: w} 63 | log := lh.log.WithFields(logrus.Fields{ 64 | "http.req.path": r.URL.Path, 65 | "http.req.method": r.Method, 66 | "http.req.id": requestID.String(), 67 | }) 68 | if v, ok := r.Context().Value(ctxKeySessionID{}).(string); ok { 69 | log = log.WithField("session", v) 70 | } 71 | log.Debug("request started") 72 | defer func() { 73 | log.WithFields(logrus.Fields{ 74 | "http.resp.took_ms": int64(time.Since(start) / time.Millisecond), 75 | "http.resp.status": rr.status, 76 | "http.resp.bytes": rr.b}).Debugf("request complete") 77 | }() 78 | 79 | ctx = context.WithValue(ctx, ctxKeyLog{}, log) 80 | r = r.WithContext(ctx) 81 | lh.next.ServeHTTP(rr, r) 82 | } 83 | 84 | func ensureSessionID(next http.Handler) http.HandlerFunc { 85 | return func(w http.ResponseWriter, r *http.Request) { 86 | var sessionID string 87 | c, err := r.Cookie(cookieSessionID) 88 | if err == http.ErrNoCookie { 89 | u, _ := uuid.NewRandom() 90 | sessionID = u.String() 91 | http.SetCookie(w, &http.Cookie{ 92 | Name: cookieSessionID, 93 | Value: sessionID, 94 | MaxAge: cookieMaxAge, 95 | }) 96 | } else if err != nil { 97 | return 98 | } else { 99 | sessionID = c.Value 100 | } 101 | ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID) 102 | r = r.WithContext(ctx) 103 | next.ServeHTTP(w, r) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/frontend/money/money.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package money 16 | 17 | import ( 18 | "errors" 19 | 20 | pb "github.com/signalfx/microservices-demo/src/frontend/genproto" 21 | ) 22 | 23 | const ( 24 | nanosMin = -999999999 25 | nanosMax = +999999999 26 | nanosMod = 1000000000 27 | ) 28 | 29 | var ( 30 | ErrInvalidValue = errors.New("one of the specified money values is invalid") 31 | ErrMismatchingCurrency = errors.New("mismatching currency codes") 32 | ) 33 | 34 | // IsValid checks if specified value has a valid units/nanos signs and ranges. 35 | func IsValid(m pb.Money) bool { 36 | return signMatches(m) && validNanos(m.GetNanos()) 37 | } 38 | 39 | func signMatches(m pb.Money) bool { 40 | return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) 41 | } 42 | 43 | func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } 44 | 45 | // IsZero returns true if the specified money value is equal to zero. 46 | func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } 47 | 48 | // IsPositive returns true if the specified money value is valid and is 49 | // positive. 50 | func IsPositive(m pb.Money) bool { 51 | return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) 52 | } 53 | 54 | // IsNegative returns true if the specified money value is valid and is 55 | // negative. 56 | func IsNegative(m pb.Money) bool { 57 | return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) 58 | } 59 | 60 | // AreSameCurrency returns true if values l and r have a currency code and 61 | // they are the same values. 62 | func AreSameCurrency(l, r pb.Money) bool { 63 | return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" 64 | } 65 | 66 | // AreEquals returns true if values l and r are the equal, including the 67 | // currency. This does not check validity of the provided values. 68 | func AreEquals(l, r pb.Money) bool { 69 | return l.GetCurrencyCode() == r.GetCurrencyCode() && 70 | l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() 71 | } 72 | 73 | // Negate returns the same amount with the sign negated. 74 | func Negate(m pb.Money) pb.Money { 75 | return pb.Money{ 76 | Units: -m.GetUnits(), 77 | Nanos: -m.GetNanos(), 78 | CurrencyCode: m.GetCurrencyCode()} 79 | } 80 | 81 | // Must panics if the given error is not nil. This can be used with other 82 | // functions like: "m := Must(Sum(a,b))". 83 | func Must(v pb.Money, err error) pb.Money { 84 | if err != nil { 85 | panic(err) 86 | } 87 | return v 88 | } 89 | 90 | // Sum adds two values. Returns an error if one of the values are invalid or 91 | // currency codes are not matching (unless currency code is unspecified for 92 | // both). 93 | func Sum(l, r pb.Money) (pb.Money, error) { 94 | if !IsValid(l) || !IsValid(r) { 95 | return pb.Money{}, ErrInvalidValue 96 | } else if l.GetCurrencyCode() != r.GetCurrencyCode() { 97 | return pb.Money{}, ErrMismatchingCurrency 98 | } 99 | units := l.GetUnits() + r.GetUnits() 100 | nanos := l.GetNanos() + r.GetNanos() 101 | 102 | if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { 103 | // same sign 104 | units += int64(nanos / nanosMod) 105 | nanos = nanos % nanosMod 106 | } else { 107 | // different sign. nanos guaranteed to not to go over the limit 108 | if units > 0 { 109 | units-- 110 | nanos += nanosMod 111 | } else { 112 | units++ 113 | nanos -= nanosMod 114 | } 115 | } 116 | 117 | return pb.Money{ 118 | Units: units, 119 | Nanos: nanos, 120 | CurrencyCode: l.GetCurrencyCode()}, nil 121 | } 122 | 123 | // MultiplySlow is a slow multiplication operation done through adding the value 124 | // to itself n-1 times. 125 | func MultiplySlow(m pb.Money, n uint32) pb.Money { 126 | out := m 127 | for n > 1 { 128 | out = Must(Sum(out, m)) 129 | n-- 130 | } 131 | return out 132 | } 133 | -------------------------------------------------------------------------------- /src/frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/favicon.ico -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_CartIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_CheckOutIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_CurrencyIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_DownArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_FacebookIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_HelpIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_InstagramIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_NavLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | Hipster 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_PinterestIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_ProfileIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_SearchIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_TwitterIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_UpDownControl.svg: -------------------------------------------------------------------------------- 1 | 2 | Hipster 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/frontend/static/icons/Hipster_YoutubeIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Hipster 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/frontend/static/images/Advert2BannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/images/Advert2BannerImage.png -------------------------------------------------------------------------------- /src/frontend/static/images/AdvertBannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/images/AdvertBannerImage.png -------------------------------------------------------------------------------- /src/frontend/static/images/HeroBannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/images/HeroBannerImage.png -------------------------------------------------------------------------------- /src/frontend/static/images/HeroBannerImage2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/images/HeroBannerImage2.png -------------------------------------------------------------------------------- /src/frontend/static/images/VRHeadsets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/images/VRHeadsets.png -------------------------------------------------------------------------------- /src/frontend/static/img/products/air-plant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/air-plant.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/barista-kit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/barista-kit.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/camera-lens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/camera-lens.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/camp-mug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/camp-mug.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/city-bike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/city-bike.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/credits.txt: -------------------------------------------------------------------------------- 1 | film-camera.jpg,CC0 Public Domain,https://pxhere.com/en/photo/829555 2 | camera-lens.jpg,CC0 Public Domain,https://pxhere.com/en/photo/670041 3 | air-plant.jpg,,https://unsplash.com/photos/uUwEAW5jFLE 4 | camp-mug.jpg,,https://unsplash.com/photos/h9VhRlMfVkg 5 | record-player.jpg,,https://unsplash.com/photos/pEEHFSX1vak 6 | city-bike.jpg,,https://unsplash.com/photos/Lpe9u9etwMU 7 | typewriter.jpg,,https://unsplash.com/photos/mk7D-4UCfmg 8 | barista-kit.jpg,,https://unsplash.com/photos/ZiRyGGIpRCw 9 | terrarium.jpg,,https://unsplash.com/photos/E9QYLj0724Y 10 | -------------------------------------------------------------------------------- /src/frontend/static/img/products/film-camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/film-camera.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/record-player.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/record-player.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/terrarium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/terrarium.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/typewriter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalfx/microservices-demo/57d144651f438e254adb22f952957dfda73d6198/src/frontend/static/img/products/typewriter.jpg -------------------------------------------------------------------------------- /src/frontend/static/styles/cart.css: -------------------------------------------------------------------------------- 1 | .cart-bg { 2 | background: #f0f0f0; 3 | } 4 | 5 | .cart .form-control { 6 | border: none; 7 | border-radius: 0; 8 | -webkit-appearance: none; 9 | } 10 | 11 | .cart h3 { 12 | font-size: 36px; 13 | } 14 | 15 | .cart form label { 16 | color: #707070; 17 | } 18 | 19 | .empty-btn { 20 | margin-right: 20px; 21 | } 22 | 23 | .btn { 24 | border-radius: 0; 25 | color: #111111; 26 | background: #e9ecef; 27 | border: 1px solid #d1d7dc; 28 | font-size: 18px; 29 | padding: 10px 15px; 30 | text-transform: uppercase; 31 | letter-spacing: 1.8px; 32 | } 33 | 34 | .btn-info { 35 | border: none; 36 | background: #4cc8c6; 37 | } 38 | 39 | .center-contents > * { 40 | margin: auto; 41 | } 42 | 43 | .product-item { 44 | max-width: 540px; 45 | margin: auto; 46 | margin-bottom: 30px; 47 | } 48 | 49 | .product-item .image { 50 | max-width: 180px; 51 | padding: 0; 52 | } 53 | 54 | .product-item .image img { 55 | width: 100%; 56 | height: 100%; 57 | object-fit: cover; 58 | } 59 | 60 | .product-item .text { 61 | background: white; 62 | padding: 25px 30px; 63 | } 64 | 65 | .product-item .text h4 { 66 | font-size: 24px; 67 | letter-spacing: 4.8px; 68 | margin-bottom: 0; 69 | } 70 | 71 | .product-item .text p { 72 | margin-bottom: 20px; 73 | } 74 | 75 | .product-item .text small { 76 | font-size: 18px; 77 | } 78 | 79 | .product-item .text .details { 80 | font-size: 18px; 81 | } 82 | 83 | .order-summary { 84 | font-size: 24px; 85 | color: #000000; 86 | margin-bottom: 30px; 87 | } 88 | 89 | .order-summary p { 90 | font-size: 18px; 91 | } 92 | 93 | .checkout h3 { 94 | font-size: 36px; 95 | margin-bottom: 30px; 96 | } 97 | .last-row { 98 | margin-top: 30px; 99 | } -------------------------------------------------------------------------------- /src/frontend/static/styles/order.css: -------------------------------------------------------------------------------- 1 | .order { 2 | background: #f0f0f0; 3 | } 4 | 5 | .order .container { 6 | border-top: 1px solid #b4b2bb; 7 | } 8 | 9 | .order-logo { 10 | height: 100px; 11 | margin-bottom: 35px; 12 | } 13 | 14 | .order h3 { 15 | font-size: 36px; 16 | margin-bottom: 40px; 17 | } 18 | 19 | .order p { 20 | font-size: 24px; 21 | margin-bottom: 0; 22 | color: #707070; 23 | } 24 | 25 | .order .mg-bt { 26 | color: #111111; 27 | margin-bottom: 35px; 28 | } 29 | 30 | .order .btn { 31 | margin: auto; 32 | } -------------------------------------------------------------------------------- /src/frontend/templates/ad.html: -------------------------------------------------------------------------------- 1 | {{ define "text_ad" }} 2 |
3 | 9 |
10 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/error.html: -------------------------------------------------------------------------------- 1 | {{ define "error" }} 2 | {{ template "header" . }} 3 |
4 |
5 |
6 |

Uh, oh!

7 |

Something has failed. Below are some details for debugging.

8 | 9 |

HTTP Status: {{.status_code}} {{.status}}

10 |
12 |                     {{- .error -}}
13 |                 
14 |
15 |
16 |
17 | 18 | {{ template "footer" . }} 19 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/footer.html: -------------------------------------------------------------------------------- 1 | {{ define "footer" }} 2 | 3 |
4 | 11 |
12 | 15 | 16 | 17 | 18 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/header.html: -------------------------------------------------------------------------------- 1 | {{ define "header" }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Online Boutique 10 | 12 | 13 | 14 | 15 | 16 | 17 | {{- if and $.rum_realm $.rum_auth }} 18 | 19 | 20 | {{- end }} 21 | 22 | 23 | 24 |
25 | 43 | 60 |
61 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/home.html: -------------------------------------------------------------------------------- 1 | {{ define "home" }} 2 | 3 | {{ template "header" . }} 4 |
5 |
6 |
7 | icon 8 |
9 |
10 | 11 |
12 |
13 |
14 | icon 15 |
16 |
17 | {{ range $.products }} 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 | {{ .Item.Name }} 27 |
28 |
29 | 30 | {{ renderMoney .Price }} 31 | 32 |
33 |
34 |
35 |
36 | {{ end }} 37 |
38 |
39 |
40 |
41 | 42 | {{ template "footer" . }} 43 | 44 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/order.html: -------------------------------------------------------------------------------- 1 | {{ define "order" }} 2 | {{ template "header" . }} 3 |
4 |
5 |
6 |
7 |
8 | 9 |

10 | Your order is complete! 11 |

12 |

Order Confirmation ID

13 |

{{.order.OrderId}}

14 |

Shipping Tracking ID

15 |

{{.order.ShippingTrackingId}}

16 |

Shipping Cost

17 |

{{renderMoney .order.ShippingCost}}

18 |

Total Paid

19 |

{{renderMoney .total_paid}}

20 |
21 |
22 | 23 |
24 |
25 |
26 | Keep Browsing 27 |
28 |
29 | {{ if $.recommendations }} 30 | {{ template "recommendations" $.recommendations }} 31 | {{ end }} 32 |
33 |
34 | 35 | {{ template "footer" . }} 36 | {{ end }} 37 | -------------------------------------------------------------------------------- /src/frontend/templates/product.html: -------------------------------------------------------------------------------- 1 | {{ define "product" }} 2 | {{ template "header" . }} 3 | 28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 |

{{$.product.Item.Name}}

37 | 38 |

39 | {{ renderMoney $.product.Price}} 40 |

41 |
42 |
Product Description:
43 | {{$.product.Item.Description}} 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 |
52 | 60 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | {{ if $.recommendations}} 69 | {{ template "recommendations" $.recommendations }} 70 | {{ end }} 71 | 72 | {{ with $.ad }}{{ template "text_ad" . }}{{ end}} 73 | 74 |
75 |
76 | {{ template "footer" . }} 77 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/recommendations.html: -------------------------------------------------------------------------------- 1 | {{ define "recommendations" }} 2 |
3 |
4 |
5 | icon 6 |
7 |
8 | {{range . }} 9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 |
17 | {{ .Name }} 18 |
19 |
20 |
21 |
22 | {{ end }} 23 |
24 |
25 |
26 | {{ end }} -------------------------------------------------------------------------------- /src/loadgenerator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | 3 | RUN apk --no-cache add g++ zeromq-dev libffi-dev file make gcc musl-dev bash curl 4 | 5 | WORKDIR /app 6 | COPY . . 7 | 8 | # To work around https://github.com/gevent/gevent/issues/1899 9 | RUN pip install gevent==21.1.2 --no-build-isolation 10 | 11 | RUN pip install -U -r requirements.txt 12 | 13 | RUN chmod +x ./loadgen.sh 14 | ENTRYPOINT ./loadgen.sh 15 | -------------------------------------------------------------------------------- /src/loadgenerator/loadgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | trap "exit" TERM 19 | 20 | if [[ -z "${FRONTEND_ADDR}" ]]; then 21 | echo >&2 "FRONTEND_ADDR not specified" 22 | exit 1 23 | fi 24 | 25 | set -x 26 | 27 | # if one request to the frontend fails, then exit 28 | STATUSCODE=$(curl --silent --output /dev/stderr --write-out "%{http_code}" http://${FRONTEND_ADDR}) 29 | if test $STATUSCODE -ne 200; then 30 | echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" 31 | exit 1 32 | fi 33 | 34 | # else, run loadgen 35 | locust --host="http://${FRONTEND_ADDR}" -u "${USERS:-10}" --autostart 36 | -------------------------------------------------------------------------------- /src/loadgenerator/locustfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import random 18 | from locust import HttpUser, TaskSet, between 19 | 20 | products = [ 21 | '0PUK6V6EV0', 22 | '1YMWWN1N4O', 23 | '2ZYFJ3GM2N', 24 | '66VCHSJNUP', 25 | '6E92ZMYYFZ', 26 | '9SIQT8TOJO', 27 | 'L9ECAV7KIM', 28 | 'LS4PSXUNUM', 29 | 'OLJCESPC7Z'] 30 | 31 | def index(l): 32 | l.client.get("/") 33 | 34 | def setCurrency(l): 35 | currencies = ['EUR', 'USD', 'JPY', 'CAD'] 36 | l.client.post("/setCurrency", 37 | {'currency_code': random.choice(currencies)}) 38 | 39 | def browseProduct(l): 40 | l.client.get("/product/" + random.choice(products)) 41 | 42 | def viewCart(l): 43 | l.client.get("/cart") 44 | 45 | def addToCart(l): 46 | product = random.choice(products) 47 | l.client.get("/product/" + product) 48 | l.client.post("/cart", { 49 | 'product_id': product, 50 | 'quantity': random.choice([1,2,3,4,5,10])}) 51 | 52 | def checkout(l): 53 | addToCart(l) 54 | l.client.post("/cart/checkout", { 55 | 'email': 'someone@example.com', 56 | 'street_address': '270 Brannan St', 57 | 'zip_code': '94107', 58 | 'city': 'Francisco', 59 | 'state': 'CA', 60 | 'country': 'United States', 61 | 'credit_card_number': '4432-8015-6152-0454', 62 | 'credit_card_expiration_month': '1', 63 | 'credit_card_expiration_year': '2039', 64 | 'credit_card_cvv': '672', 65 | }) 66 | 67 | class UserBehavior(TaskSet): 68 | 69 | def on_start(self): 70 | index(self) 71 | 72 | tasks = {index: 1, 73 | setCurrency: 2, 74 | browseProduct: 10, 75 | addToCart: 2, 76 | viewCart: 3, 77 | checkout: 1} 78 | 79 | class WebsiteUser(HttpUser): 80 | tasks = [UserBehavior] 81 | wait_time = between(1, 10) 82 | -------------------------------------------------------------------------------- /src/loadgenerator/requirements.in: -------------------------------------------------------------------------------- 1 | locust==2.4.1 2 | 3 | # required for CVE-2021-33503 4 | urllib3>=1.26.5 5 | -------------------------------------------------------------------------------- /src/loadgenerator/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --output-file=requirements.txt requirements.in 6 | # 7 | brotli==1.0.9 8 | # via geventhttpclient 9 | certifi==2020.12.5 10 | # via 11 | # geventhttpclient 12 | # requests 13 | chardet==4.0.0 14 | # via requests 15 | click==7.1.2 16 | # via flask 17 | configargparse==1.4 18 | # via locust 19 | flask==2.0.1 20 | # via 21 | # flask-basicauth 22 | # flask-cors 23 | # locust 24 | flask-basicauth==0.2.0 25 | # via locust 26 | flask-cors==3.0.10 27 | # via locust 28 | gevent==21.1.2 29 | # via 30 | # geventhttpclient 31 | # locust 32 | geventhttpclient==1.5.1 33 | # via locust 34 | greenlet==1.0.0 35 | # via gevent 36 | idna==2.10 37 | # via requests 38 | itsdangerous==2.0.1 39 | # via flask 40 | jinja2==3.0.1 41 | # via flask 42 | locust==2.4.1 43 | # via -r requirements.in 44 | markupsafe==2.0.1 45 | # via jinja2 46 | msgpack==1.0.2 47 | # via locust 48 | psutil==5.8.0 49 | # via locust 50 | pyzmq==22.3.0 51 | # via locust 52 | requests==2.25.1 53 | # via locust 54 | roundrobin==0.0.2 55 | # via locust 56 | six==1.15.0 57 | # via 58 | # flask-cors 59 | # geventhttpclient 60 | typing-extensions==3.10.0.2 61 | # via locust 62 | urllib3==1.26.7 63 | # via 64 | # -r requirements.in 65 | # requests 66 | werkzeug==2.0.1 67 | # via 68 | # flask 69 | # locust 70 | zope-event==4.5.0 71 | # via gevent 72 | zope-interface==5.3.0 73 | # via gevent 74 | 75 | # The following packages are considered to be unsafe in a requirements file: 76 | # setuptools 77 | -------------------------------------------------------------------------------- /src/paymentservice/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/paymentservice/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /src/paymentservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine as base 2 | 3 | FROM base as builder 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install --only=production 10 | 11 | FROM base 12 | 13 | RUN rm -r /usr/local/lib/node_modules 14 | 15 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 16 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 17 | chmod +x /bin/grpc_health_probe 18 | 19 | WORKDIR /usr/src/app 20 | 21 | COPY --from=builder /usr/src/app/node_modules ./node_modules 22 | 23 | COPY . . 24 | 25 | EXPOSE 50051 26 | 27 | ENTRYPOINT [ "node", "-r", "./tracing.js", "index.js" ] 28 | -------------------------------------------------------------------------------- /src/paymentservice/README.md: -------------------------------------------------------------------------------- 1 | # paymentservice 2 | 3 | ## Environment Variables 4 | 5 | `API_TOKEN_FAILURE_RATE`: float [0, 1] (default 0.0), Percentage of requests that should be rejected with "Invalid API Token" error 6 | `SUCCESS_PAYMENT_SERVICE_DURATION_MILLIS`: int (default 200); Artificial delay added to successful requests 7 | `ERROR_PAYMENT_SERVICE_DURATION_MILLIS`: int (default 1000); Artificial delay added to failed requests 8 | 9 | `SERIALIZATION_FAILURE_RATE`: float [0, 1] (default 0.0); Percentage of requests that should fail with a "Serialization failure" error before the payment request is made. Logs with stack trace emitted. 10 | -------------------------------------------------------------------------------- /src/paymentservice/examples/charge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using: https://github.com/njpatel/grpcc 3 | * src/checkoutservice $ grpcc --insecure -p proto/demo.proto -a localhost:3002 -s hipstershop.PaymentService --exec examples/charge.js 4 | */ 5 | client.charge( 6 | { 7 | amount: { 8 | currency_code: 'USD', 9 | units: 1, 10 | nanos: 0, 11 | }, 12 | credit_card: { 13 | credit_card_number: '4009366231016609', 14 | credit_card_cvv: 123, 15 | credit_card_expiration_year: 2025, 16 | credit_card_expiration_month: 12, 17 | }, 18 | }, 19 | printReply 20 | ); 21 | -------------------------------------------------------------------------------- /src/paymentservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # protos are loaded dynamically for node, simply copies over the proto. 18 | mkdir -p proto 19 | cp -r ../../pb/* ./proto 20 | -------------------------------------------------------------------------------- /src/paymentservice/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | require('./tracing'); 20 | 21 | const path = require('path'); 22 | const HipsterShopServer = require('./server'); 23 | 24 | const PORT = process.env['PORT']; 25 | const PROTO_PATH = path.join(__dirname, '/proto/'); 26 | 27 | const server = new HipsterShopServer(PROTO_PATH, PORT); 28 | 29 | server.listen(); 30 | -------------------------------------------------------------------------------- /src/paymentservice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paymentservice", 3 | "version": "0.0.1", 4 | "description": "Payment Microservice demo", 5 | "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "semistandard *.js" 10 | }, 11 | "author": "Jonathan Lui", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@grpc/grpc-js": "^1.2.12", 15 | "@grpc/proto-loader": "^0.6.1", 16 | "@opentelemetry/api": "~0.18.1", 17 | "@opentelemetry/core": "~0.18.1", 18 | "@opentelemetry/exporter-zipkin": "~0.18.1", 19 | "@opentelemetry/instrumentation-grpc": "~0.18.1", 20 | "@opentelemetry/node": "~0.18.1", 21 | "@opentelemetry/propagator-b3": "~0.18.1", 22 | "@opentelemetry/tracing": "~0.18.1", 23 | "pino": "^5.17.0", 24 | "simple-card-validator": "^1.1.0", 25 | "uuid": "^3.2.1" 26 | }, 27 | "devDependencies": { 28 | "semistandard": "^12.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/paymentservice/proto/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /src/paymentservice/server.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const path = require('path'); 16 | const grpc = require('@grpc/grpc-js'); 17 | const pino = require('pino'); 18 | const protoLoader = require('@grpc/proto-loader'); 19 | const { getSpan, context } = require("@opentelemetry/api"); 20 | 21 | const charge = require('./charge'); 22 | 23 | const logger = pino({ 24 | name: 'paymentservice-server', 25 | messageKey: 'message', 26 | changeLevelName: 'severity', 27 | useLevelLabels: true, 28 | timestamp: pino.stdTimeFunctions.unixTime, 29 | mixin() { 30 | const span = getSpan(context.active()) 31 | if (!span) { 32 | return {}; 33 | } 34 | const { traceId, spanId } = span.context(); 35 | return { 36 | trace_id: traceId.slice(-16), // convert to 64-bit format 37 | span_id: spanId, 38 | 'service.name': 'paymentservice' 39 | }; 40 | }, 41 | }); 42 | 43 | class HipsterShopServer { 44 | constructor(protoRoot, port = HipsterShopServer.PORT) { 45 | this.port = port; 46 | 47 | this.packages = { 48 | hipsterShop: this.loadProto(path.join(protoRoot, 'demo.proto')), 49 | health: this.loadProto( 50 | path.join(protoRoot, 'grpc/health/v1/health.proto') 51 | ), 52 | }; 53 | 54 | this.server = new grpc.Server(); 55 | this.loadAllProtos(protoRoot); 56 | } 57 | 58 | /** 59 | * Handler for PaymentService.Charge. 60 | * @param {*} call { ChargeRequest } 61 | * @param {*} callback fn(err, ChargeResponse) 62 | */ 63 | static ChargeServiceHandler(call, callback) { 64 | try { 65 | logger.info( 66 | `PaymentService#Charge invoked with request ${JSON.stringify( 67 | call.request 68 | )}` 69 | ); 70 | charge(call.request) 71 | .then((response) => { 72 | callback(null, response); 73 | }) 74 | .catch((err) => { 75 | callback(err); 76 | }); 77 | } catch (err) { 78 | console.warn(err); 79 | callback(err); 80 | } 81 | } 82 | 83 | static CheckHandler(call, callback) { 84 | callback(null, { status: 'SERVING' }); 85 | } 86 | 87 | listen() { 88 | this.server.bindAsync( 89 | `0.0.0.0:${this.port}`, 90 | grpc.ServerCredentials.createInsecure(), 91 | (err, port) => { 92 | if (err != null) { 93 | return console.error(err); 94 | } 95 | console.log(`PaymentService grpc server listening on ${port}`); 96 | this.server.start(); 97 | }, 98 | ); 99 | } 100 | 101 | loadProto(path) { 102 | const packageDefinition = protoLoader.loadSync(path, { 103 | keepCase: true, 104 | longs: String, 105 | enums: String, 106 | defaults: true, 107 | oneofs: true, 108 | }); 109 | return grpc.loadPackageDefinition(packageDefinition); 110 | } 111 | 112 | loadAllProtos(protoRoot) { 113 | const hipsterShopPackage = this.packages.hipsterShop.hipstershop; 114 | const healthPackage = this.packages.health.grpc.health.v1; 115 | 116 | this.server.addService(hipsterShopPackage.PaymentService.service, { 117 | charge: HipsterShopServer.ChargeServiceHandler.bind(this), 118 | }); 119 | 120 | this.server.addService(healthPackage.Health.service, { 121 | check: HipsterShopServer.CheckHandler.bind(this), 122 | }); 123 | } 124 | } 125 | 126 | HipsterShopServer.PORT = process.env.PORT; 127 | 128 | module.exports = HipsterShopServer; 129 | -------------------------------------------------------------------------------- /src/paymentservice/tracing.js: -------------------------------------------------------------------------------- 1 | const { NodeTracerProvider } = require('@opentelemetry/node'); 2 | const { B3MultiPropagator } = require('@opentelemetry/propagator-b3'); 3 | const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); 4 | const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); 5 | const { BatchSpanProcessor, ConsoleSpanExporter } = require('@opentelemetry/tracing'); 6 | const { registerInstrumentations } = require('@opentelemetry/instrumentation'); 7 | 8 | const provider = new NodeTracerProvider(); 9 | provider.register({ 10 | propagator: new B3MultiPropagator(), 11 | }); 12 | 13 | registerInstrumentations({ 14 | instrumentations: [ 15 | new GrpcInstrumentation(), 16 | ], 17 | }); 18 | 19 | const exporter = new ZipkinExporter({ 20 | serviceName: 'paymentservice', 21 | url: process.env.SIGNALFX_ENDPOINT_URL, 22 | }); 23 | 24 | provider.addSpanProcessor(new BatchSpanProcessor(exporter)); 25 | 26 | const CONSOLE_SPAN = process.env['CONSOLE_SPAN']; 27 | if (CONSOLE_SPAN === 'true') { 28 | provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter(), { bufferTimeout: 1000 })); 29 | } 30 | -------------------------------------------------------------------------------- /src/productcatalogservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | RUN apk add --no-cache ca-certificates git 3 | 4 | WORKDIR /app 5 | COPY . . 6 | RUN go build -o /go/bin/productcatalogservice . 7 | 8 | FROM alpine AS release 9 | RUN apk add --no-cache ca-certificates 10 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 11 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 12 | chmod +x /bin/grpc_health_probe 13 | WORKDIR /productcatalogservice 14 | COPY --from=builder /go/bin/productcatalogservice ./server 15 | COPY products.json . 16 | EXPOSE 3550 17 | ENTRYPOINT ["/productcatalogservice/server"] 18 | 19 | -------------------------------------------------------------------------------- /src/productcatalogservice/README.md: -------------------------------------------------------------------------------- 1 | # productcatalogservice 2 | 3 | ## Dynamic catalog reloading / artificial delay 4 | 5 | This service has a "dynamic catalog reloading" feature that is purposefully 6 | not well implemented. The goal of this feature is to allow you to modify the 7 | `products.json` file and have the changes be picked up without having to 8 | restart the service. 9 | 10 | However, this feature is bugged: the catalog is actually reloaded on each 11 | request, introducing a noticeable delay in the frontend. This delay will also 12 | show up in profiling tools: the `parseCatalog` function will take more than 80% 13 | of the CPU time. 14 | 15 | You can trigger this feature (and the delay) by sending a `USR1` signal and 16 | remove it (if needed) by sending a `USR2` signal: 17 | 18 | ``` 19 | # Trigger bug 20 | kubectl exec \ 21 | $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ 22 | -c server -- kill -USR1 1 23 | # Remove bug 24 | kubectl exec \ 25 | $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ 26 | -c server -- kill -USR2 1 27 | ``` 28 | 29 | ## Latency injection 30 | 31 | This service has an `EXTRA_LATENCY` environment variable. This will inject a sleep for the specified [time.Duration](https://golang.org/pkg/time/#ParseDuration) on every call to 32 | to the server. 33 | 34 | For example, use `EXTRA_LATENCY="5.5s"` to sleep for 5.5 seconds on every request. 35 | -------------------------------------------------------------------------------- /src/productcatalogservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$(go env GOPATH)/bin 20 | protodir=../../pb 21 | outdir=./genproto 22 | 23 | protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto 24 | -------------------------------------------------------------------------------- /src/productcatalogservice/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/signalfx/microservices-demo/src/productcatalogservice 2 | 3 | go 1.22 4 | 5 | require ( 6 | cloud.google.com/go/profiler v0.4.1 7 | github.com/golang/protobuf v1.5.4 8 | github.com/google/go-cmp v0.6.0 9 | github.com/opentracing/opentracing-go v1.2.0 10 | github.com/signalfx/signalfx-go-tracing v1.12.0 11 | github.com/signalfx/signalfx-go-tracing/contrib/google.golang.org/grpc v1.12.0 12 | github.com/sirupsen/logrus v1.9.3 13 | go.opencensus.io v0.24.0 14 | google.golang.org/grpc v1.66.0 15 | google.golang.org/protobuf v1.34.2 16 | ) 17 | 18 | require ( 19 | cloud.google.com/go v0.115.0 // indirect 20 | cloud.google.com/go/auth v0.6.0 // indirect 21 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 22 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 23 | github.com/go-logfmt/logfmt v0.5.0 // indirect 24 | github.com/go-logr/logr v1.4.1 // indirect 25 | github.com/go-logr/stdr v1.2.2 // indirect 26 | github.com/go-stack/stack v1.8.0 // indirect 27 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 28 | github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect 29 | github.com/google/s2a-go v0.1.7 // indirect 30 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 31 | github.com/googleapis/gax-go/v2 v2.12.5 // indirect 32 | github.com/josharian/intern v1.0.0 // indirect 33 | github.com/mailru/easyjson v0.7.7 // indirect 34 | github.com/philhofer/fwd v1.1.1 // indirect 35 | github.com/signalfx/golib v2.5.1+incompatible // indirect 36 | github.com/tinylib/msgp v1.1.6 // indirect 37 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 38 | go.opentelemetry.io/otel v1.24.0 // indirect 39 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 40 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 41 | golang.org/x/crypto v0.26.0 // indirect 42 | golang.org/x/net v0.28.0 // indirect 43 | golang.org/x/oauth2 v0.21.0 // indirect 44 | golang.org/x/sync v0.8.0 // indirect 45 | golang.org/x/sys v0.23.0 // indirect 46 | golang.org/x/text v0.17.0 // indirect 47 | golang.org/x/time v0.5.0 // indirect 48 | google.golang.org/api v0.186.0 // indirect 49 | google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect 50 | google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect 51 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /src/productcatalogservice/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": [ 3 | { 4 | "id": "OLJCESPC7Z", 5 | "name": "Vintage Typewriter", 6 | "description": "This typewriter looks good in your living room.", 7 | "picture": "/static/img/products/typewriter.jpg", 8 | "priceUsd": { 9 | "currencyCode": "USD", 10 | "units": 67, 11 | "nanos": 990000000 12 | }, 13 | "categories": ["vintage"] 14 | }, 15 | { 16 | "id": "66VCHSJNUP", 17 | "name": "Vintage Camera Lens", 18 | "description": "You won't have a camera to use it and it probably doesn't work anyway.", 19 | "picture": "/static/img/products/camera-lens.jpg", 20 | "priceUsd": { 21 | "currencyCode": "USD", 22 | "units": 12, 23 | "nanos": 490000000 24 | }, 25 | "categories": ["photography", "vintage"] 26 | }, 27 | { 28 | "id": "1YMWWN1N4O", 29 | "name": "Home Barista Kit", 30 | "description": "Always wanted to brew coffee with Chemex and Aeropress at home?", 31 | "picture": "/static/img/products/barista-kit.jpg", 32 | "priceUsd": { 33 | "currencyCode": "USD", 34 | "units": 124 35 | }, 36 | "categories": ["cookware"] 37 | }, 38 | { 39 | "id": "L9ECAV7KIM", 40 | "name": "Terrarium", 41 | "description": "This terrarium will looks great in your white painted living room.", 42 | "picture": "/static/img/products/terrarium.jpg", 43 | "priceUsd": { 44 | "currencyCode": "USD", 45 | "units": 36, 46 | "nanos": 450000000 47 | }, 48 | "categories": ["gardening"] 49 | }, 50 | { 51 | "id": "2ZYFJ3GM2N", 52 | "name": "Film Camera", 53 | "description": "This camera looks like it's a film camera, but it's actually digital.", 54 | "picture": "/static/img/products/film-camera.jpg", 55 | "priceUsd": { 56 | "currencyCode": "USD", 57 | "units": 2245 58 | }, 59 | "categories": ["photography", "vintage"] 60 | }, 61 | { 62 | "id": "0PUK6V6EV0", 63 | "name": "Vintage Record Player", 64 | "description": "It still works.", 65 | "picture": "/static/img/products/record-player.jpg", 66 | "priceUsd": { 67 | "currencyCode": "USD", 68 | "units": 65, 69 | "nanos": 500000000 70 | }, 71 | "categories": ["music", "vintage"] 72 | }, 73 | { 74 | "id": "LS4PSXUNUM", 75 | "name": "Metal Camping Mug", 76 | "description": "You probably don't go camping that often but this is better than plastic cups.", 77 | "picture": "/static/img/products/camp-mug.jpg", 78 | "priceUsd": { 79 | "currencyCode": "USD", 80 | "units": 24, 81 | "nanos": 330000000 82 | }, 83 | "categories": ["cookware"] 84 | }, 85 | { 86 | "id": "9SIQT8TOJO", 87 | "name": "City Bike", 88 | "description": "This single gear bike probably cannot climb the hills of San Francisco.", 89 | "picture": "/static/img/products/city-bike.jpg", 90 | "priceUsd": { 91 | "currencyCode": "USD", 92 | "units": 789, 93 | "nanos": 500000000 94 | }, 95 | "categories": ["cycling"] 96 | }, 97 | { 98 | "id": "6E92ZMYYFZ", 99 | "name": "Air Plant", 100 | "description": "Have you ever wondered whether air plants need water? Buy one and figure out.", 101 | "picture": "/static/img/products/air-plant.jpg", 102 | "priceUsd": { 103 | "currencyCode": "USD", 104 | "units": 12, 105 | "nanos": 300000000 106 | }, 107 | "categories": ["gardening"] 108 | } 109 | ] 110 | } -------------------------------------------------------------------------------- /src/productcatalogservice/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/golang/protobuf/proto" 22 | "github.com/google/go-cmp/cmp" 23 | pb "github.com/signalfx/microservices-demo/src/productcatalogservice/genproto" 24 | "go.opencensus.io/plugin/ocgrpc" 25 | "google.golang.org/grpc" 26 | "google.golang.org/grpc/codes" 27 | "google.golang.org/grpc/status" 28 | ) 29 | 30 | func TestServer(t *testing.T) { 31 | ctx := context.Background() 32 | addr := run("0") 33 | conn, err := grpc.Dial(addr, 34 | grpc.WithInsecure(), 35 | grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | defer conn.Close() 40 | client := pb.NewProductCatalogServiceClient(conn) 41 | res, err := client.ListProducts(ctx, &pb.Empty{}) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if diff := cmp.Diff(res.Products, parseCatalog(ctx), cmp.Comparer(proto.Equal)); diff != "" { 46 | t.Error(diff) 47 | } 48 | 49 | got, err := client.GetProduct(ctx, &pb.GetProductRequest{Id: "OLJCESPC7Z"}) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if want := parseCatalog(ctx)[0]; !proto.Equal(got, want) { 54 | t.Errorf("got %v, want %v", got, want) 55 | } 56 | _, err = client.GetProduct(ctx, &pb.GetProductRequest{Id: "N/A"}) 57 | if got, want := status.Code(err), codes.NotFound; got != want { 58 | t.Errorf("got %s, want %s", got, want) 59 | } 60 | 61 | sres, err := client.SearchProducts(ctx, &pb.SearchProductsRequest{Query: "typewriter"}) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if diff := cmp.Diff(sres.Results, []*pb.Product{parseCatalog(ctx)[0]}, cmp.Comparer(proto.Equal)); diff != "" { 66 | t.Error(diff) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/recommendationservice/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /src/recommendationservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-alpine3.14 2 | 3 | RUN apk add --no-cache \ 4 | g++ \ 5 | musl-dev \ 6 | libffi-dev 7 | 8 | COPY requirements.txt . 9 | RUN pip install --no-cache-dir -r requirements.txt 10 | 11 | # show python logs as they occur 12 | ENV PYTHONUNBUFFERED=0 13 | 14 | # download the grpc health probe 15 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 16 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 17 | chmod +x /bin/grpc_health_probe 18 | 19 | WORKDIR /recommendationservice 20 | 21 | # add files into working directory 22 | COPY . . 23 | 24 | # set listen port 25 | ENV PORT "8080" 26 | EXPOSE 8080 27 | 28 | ENTRYPOINT ["python", "/recommendationservice/recommendation_server.py"] 29 | -------------------------------------------------------------------------------- /src/recommendationservice/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | import grpc 19 | import demo_pb2 20 | import demo_pb2_grpc 21 | 22 | from logger import getJSONLogger 23 | logger = getJSONLogger('recommendationservice-server') 24 | 25 | if __name__ == "__main__": 26 | # get port 27 | if len(sys.argv) > 1: 28 | port = sys.argv[1] 29 | else: 30 | port = "8080" 31 | 32 | # set up server stub 33 | channel = grpc.insecure_channel('localhost:'+port) 34 | stub = demo_pb2_grpc.RecommendationServiceStub(channel) 35 | # form request 36 | request = demo_pb2.ListRecommendationsRequest(user_id="test", product_ids=["test"]) 37 | # make call to server 38 | response = stub.ListRecommendations(request) 39 | logger.info(response) 40 | -------------------------------------------------------------------------------- /src/recommendationservice/fixed_propagator.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import opentelemetry.trace as trace 4 | from opentelemetry.context import Context 5 | from opentelemetry.sdk.trace.propagation.b3_format import ( 6 | B3Format, 7 | format_trace_id, 8 | format_span_id, 9 | ) 10 | from opentelemetry.trace.propagation.textmap import ( 11 | Setter, 12 | TextMapPropagatorT, 13 | ) 14 | 15 | class FixedB3Format(B3Format): 16 | 17 | def inject( 18 | self, 19 | set_in_carrier: Setter[TextMapPropagatorT], 20 | carrier: TextMapPropagatorT, 21 | context: typing.Optional[Context] = None, 22 | ) -> None: 23 | span = trace.get_current_span(context=context) 24 | 25 | span_context = span.get_context() 26 | if span_context == trace.INVALID_SPAN_CONTEXT: 27 | return 28 | 29 | sampled = (trace.TraceFlags.SAMPLED & span_context.trace_flags) != 0 30 | set_in_carrier( 31 | carrier, self.TRACE_ID_KEY, format_trace_id(span_context.trace_id), 32 | ) 33 | set_in_carrier( 34 | carrier, self.SPAN_ID_KEY, format_span_id(span_context.span_id) 35 | ) 36 | if getattr(span, 'parent', None) is not None: 37 | set_in_carrier( 38 | carrier, 39 | self.PARENT_SPAN_ID_KEY, 40 | format_span_id(span.parent.span_id), 41 | ) 42 | set_in_carrier(carrier, self.SAMPLED_KEY, "1" if sampled else "0") 43 | -------------------------------------------------------------------------------- /src/recommendationservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash 18 | set -e 19 | 20 | # script to compile python protos 21 | # 22 | # requires gRPC tools: 23 | # pip install -r requirements.txt 24 | 25 | python -m grpc_tools.protoc -I../../pb --python_out=. --grpc_python_out=. ../../pb/demo.proto 26 | -------------------------------------------------------------------------------- /src/recommendationservice/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import logging 18 | import sys 19 | from pythonjsonlogger import jsonlogger 20 | 21 | # TODO(yoshifumi) this class is duplicated since other Python services are 22 | # not sharing the modules for logging. 23 | class CustomJsonFormatter(jsonlogger.JsonFormatter): 24 | def add_fields(self, log_record, record, message_dict): 25 | super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) 26 | if not log_record.get('timestamp'): 27 | log_record['timestamp'] = record.created 28 | if log_record.get('severity'): 29 | log_record['severity'] = log_record['severity'].upper() 30 | else: 31 | log_record['severity'] = record.levelname 32 | 33 | def getJSONLogger(name): 34 | logger = logging.getLogger(name) 35 | handler = logging.StreamHandler(sys.stdout) 36 | formatter = CustomJsonFormatter('%(timestamp)s %(severity)s %(name)s %(message)s') 37 | handler.setFormatter(formatter) 38 | logger.addHandler(handler) 39 | logger.setLevel(logging.INFO) 40 | logger.propagate = False 41 | return logger 42 | -------------------------------------------------------------------------------- /src/recommendationservice/requirements.in: -------------------------------------------------------------------------------- 1 | grpcio-health-checking==1.30.0 2 | grpcio==1.53.0 3 | opentelemetry-api==0.13b0 4 | opentelemetry-instrumentation==0.13b0 5 | opentelemetry-sdk==0.13b0 6 | opentelemetry-exporter-zipkin==0.13b0 7 | opentelemetry-instrumentation-grpc==0.13b0 8 | opentelemetry-instrumentation-requests==0.13b0 9 | opentelemetry-instrumentation-sqlite3==0.13b0 10 | python-json-logger==0.1.11 11 | urllib3==1.26.6 12 | -------------------------------------------------------------------------------- /src/recommendationservice/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.9 3 | # To update, run: 4 | # 5 | # pip-compile --output-file=requirements.txt requirements.in 6 | # 7 | certifi==2023.7.22 8 | # via requests 9 | charset-normalizer==2.0.6 10 | # via requests 11 | grpcio==1.53.0 12 | # via 13 | # -r requirements.in 14 | # grpcio-health-checking 15 | # opentelemetry-instrumentation-grpc 16 | grpcio-health-checking==1.30.0 17 | # via -r requirements.in 18 | idna==3.2 19 | # via requests 20 | opentelemetry-api==0.13b0 21 | # via 22 | # -r requirements.in 23 | # opentelemetry-exporter-zipkin 24 | # opentelemetry-instrumentation 25 | # opentelemetry-instrumentation-dbapi 26 | # opentelemetry-instrumentation-grpc 27 | # opentelemetry-instrumentation-requests 28 | # opentelemetry-instrumentation-sqlite3 29 | # opentelemetry-sdk 30 | opentelemetry-exporter-zipkin==0.13b0 31 | # via -r requirements.in 32 | opentelemetry-instrumentation==0.13b0 33 | # via 34 | # -r requirements.in 35 | # opentelemetry-instrumentation-dbapi 36 | # opentelemetry-instrumentation-requests 37 | # opentelemetry-instrumentation-sqlite3 38 | opentelemetry-instrumentation-dbapi==0.13b0 39 | # via opentelemetry-instrumentation-sqlite3 40 | opentelemetry-instrumentation-grpc==0.13b0 41 | # via -r requirements.in 42 | opentelemetry-instrumentation-requests==0.13b0 43 | # via -r requirements.in 44 | opentelemetry-instrumentation-sqlite3==0.13b0 45 | # via -r requirements.in 46 | opentelemetry-sdk==0.13b0 47 | # via 48 | # -r requirements.in 49 | # opentelemetry-exporter-zipkin 50 | # opentelemetry-instrumentation-grpc 51 | protobuf==3.18.3 52 | # via grpcio-health-checking 53 | python-json-logger==0.1.11 54 | # via -r requirements.in 55 | requests==2.26.0 56 | # via 57 | # opentelemetry-exporter-zipkin 58 | # opentelemetry-instrumentation-requests 59 | urllib3==1.26.6 60 | # via 61 | # -r requirements.in 62 | # requests 63 | wrapt==1.12.1 64 | # via 65 | # opentelemetry-instrumentation 66 | # opentelemetry-instrumentation-dbapi 67 | # opentelemetry-instrumentation-sqlite3 68 | -------------------------------------------------------------------------------- /src/shippingservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | RUN apk add --no-cache ca-certificates git 3 | 4 | WORKDIR /app 5 | COPY . . 6 | RUN go build -o /go/bin/shippingservice . 7 | 8 | FROM alpine AS release 9 | RUN apk add --no-cache ca-certificates 10 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 11 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 12 | chmod +x /bin/grpc_health_probe 13 | COPY --from=builder /go/bin/shippingservice /shippingservice 14 | ENV APP_PORT=50051 15 | EXPOSE 50051 16 | ENTRYPOINT ["/shippingservice"] 17 | -------------------------------------------------------------------------------- /src/shippingservice/README.md: -------------------------------------------------------------------------------- 1 | # Shipping Service 2 | 3 | The Shipping service provides price quote, tracking IDs, and the impression of order fulfillment & shipping processes. 4 | 5 | ## Build 6 | 7 | From `src/shippingservice`, run: 8 | 9 | ``` 10 | docker build -o ./bin/shippingservice ./ 11 | ``` 12 | 13 | ## Test 14 | 15 | ``` 16 | go test . 17 | ``` 18 | -------------------------------------------------------------------------------- /src/shippingservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$(go env GOPATH)/bin 20 | protodir=../../pb 21 | outdir=./genproto 22 | 23 | protoc --proto_path=$protodir --go_out=./$outdir --go_opt=paths=source_relative --go-grpc_out=./$outdir --go-grpc_opt=paths=source_relative $protodir/demo.proto 24 | -------------------------------------------------------------------------------- /src/shippingservice/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/signalfx/microservices-demo/src/shippingservice 2 | 3 | go 1.22 4 | 5 | require ( 6 | cloud.google.com/go/profiler v0.4.1 7 | github.com/opentracing/opentracing-go v1.2.0 8 | github.com/signalfx/signalfx-go-tracing v1.12.0 9 | github.com/signalfx/signalfx-go-tracing/contrib/google.golang.org/grpc v1.12.0 10 | github.com/sirupsen/logrus v1.9.3 11 | golang.org/x/net v0.28.0 12 | google.golang.org/grpc v1.66.0 13 | google.golang.org/protobuf v1.34.2 14 | ) 15 | 16 | require ( 17 | cloud.google.com/go v0.115.0 // indirect 18 | cloud.google.com/go/auth v0.6.0 // indirect 19 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 20 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 21 | github.com/go-logfmt/logfmt v0.5.0 // indirect 22 | github.com/go-logr/logr v1.4.1 // indirect 23 | github.com/go-logr/stdr v1.2.2 // indirect 24 | github.com/go-stack/stack v1.8.0 // indirect 25 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 26 | github.com/golang/protobuf v1.5.4 // indirect 27 | github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect 28 | github.com/google/s2a-go v0.1.7 // indirect 29 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 30 | github.com/googleapis/gax-go/v2 v2.12.5 // indirect 31 | github.com/josharian/intern v1.0.0 // indirect 32 | github.com/mailru/easyjson v0.7.7 // indirect 33 | github.com/philhofer/fwd v1.1.1 // indirect 34 | github.com/signalfx/golib v2.5.1+incompatible // indirect 35 | github.com/tinylib/msgp v1.1.6 // indirect 36 | go.opencensus.io v0.24.0 // indirect 37 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 38 | go.opentelemetry.io/otel v1.24.0 // indirect 39 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 40 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 41 | golang.org/x/crypto v0.26.0 // indirect 42 | golang.org/x/oauth2 v0.21.0 // indirect 43 | golang.org/x/sync v0.8.0 // indirect 44 | golang.org/x/sys v0.23.0 // indirect 45 | golang.org/x/text v0.17.0 // indirect 46 | golang.org/x/time v0.5.0 // indirect 47 | google.golang.org/api v0.186.0 // indirect 48 | google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect 49 | google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect 50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect 51 | ) 52 | 53 | replace git.apache.org/thrift.git v0.12.1-0.20190708170704-286eee16b147 => github.com/apache/thrift v0.12.1-0.20190708170704-286eee16b147 54 | -------------------------------------------------------------------------------- /src/shippingservice/quote.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "math" 20 | ) 21 | 22 | // Quote represents a currency value. 23 | type Quote struct { 24 | Dollars uint32 25 | Cents uint32 26 | } 27 | 28 | // String representation of the Quote. 29 | func (q Quote) String() string { 30 | return fmt.Sprintf("$%d.%d", q.Dollars, q.Cents) 31 | } 32 | 33 | // CreateQuoteFromCount takes a number of items and returns a Price struct. 34 | func CreateQuoteFromCount(count int) Quote { 35 | return CreateQuoteFromFloat(quoteByCountFloat(count)) 36 | } 37 | 38 | // CreateQuoteFromFloat takes a price represented as a float and creates a Price struct. 39 | func CreateQuoteFromFloat(value float64) Quote { 40 | units, fraction := math.Modf(value) 41 | return Quote{ 42 | uint32(units), 43 | uint32(math.Trunc(fraction * 100)), 44 | } 45 | } 46 | 47 | // quoteByCountFloat takes a number of items and generates a price quote represented as a float. 48 | func quoteByCountFloat(count int) float64 { 49 | if count == 0 { 50 | return 0 51 | } 52 | count64 := float64(count) 53 | var p = 1 + (count64 * 0.2) 54 | return count64 + math.Pow(3, p) 55 | } 56 | -------------------------------------------------------------------------------- /src/shippingservice/shippingservice_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "testing" 19 | 20 | "golang.org/x/net/context" 21 | 22 | pb "github.com/signalfx/microservices-demo/src/shippingservice/genproto" 23 | ) 24 | 25 | // TestGetQuote is a basic check on the GetQuote RPC service. 26 | func TestGetQuote(t *testing.T) { 27 | s := server{} 28 | 29 | // A basic test case to test logic and protobuf interactions. 30 | req := &pb.GetQuoteRequest{ 31 | Address: &pb.Address{ 32 | StreetAddress: "Muffin Man", 33 | City: "London", 34 | State: "", 35 | Country: "England", 36 | }, 37 | Items: []*pb.CartItem{ 38 | { 39 | ProductId: "23", 40 | Quantity: 1, 41 | }, 42 | { 43 | ProductId: "46", 44 | Quantity: 3, 45 | }, 46 | }, 47 | } 48 | 49 | res, err := s.GetQuote(context.Background(), req) 50 | if err != nil { 51 | t.Errorf("TestGetQuote (%v) failed", err) 52 | } 53 | if res.CostUsd.GetUnits() != 11 || res.CostUsd.GetNanos() != 220000000 { 54 | t.Errorf("TestGetQuote: Quote value '%d.%d' does not match expected '%s'", res.CostUsd.GetUnits(), res.CostUsd.GetNanos(), "11.220000000") 55 | } 56 | } 57 | 58 | // TestShipOrder is a basic check on the ShipOrder RPC service. 59 | func TestShipOrder(t *testing.T) { 60 | s := server{} 61 | 62 | // A basic test case to test logic and protobuf interactions. 63 | req := &pb.ShipOrderRequest{ 64 | Address: &pb.Address{ 65 | StreetAddress: "Muffin Man", 66 | City: "London", 67 | State: "", 68 | Country: "England", 69 | }, 70 | Items: []*pb.CartItem{ 71 | { 72 | ProductId: "23", 73 | Quantity: 1, 74 | }, 75 | { 76 | ProductId: "46", 77 | Quantity: 3, 78 | }, 79 | }, 80 | } 81 | 82 | res, err := s.ShipOrder(context.Background(), req) 83 | if err != nil { 84 | t.Errorf("TestShipOrder (%v) failed", err) 85 | } 86 | // @todo improve quality of this test to check for a pattern such as '[A-Z]{2}-\d+-\d+'. 87 | if len(res.TrackingId) != 18 { 88 | t.Errorf("TestShipOrder: Tracking ID is malformed - has %d characters, %d expected", len(res.TrackingId), 18) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/shippingservice/tracker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "math/rand" 20 | "time" 21 | ) 22 | 23 | // seeded determines if the random number generator is ready. 24 | var seeded bool = false 25 | 26 | // CreateTrackingId generates a tracking ID. 27 | func CreateTrackingId(salt string) string { 28 | if !seeded { 29 | rand.Seed(time.Now().UnixNano()) 30 | seeded = true 31 | } 32 | 33 | return fmt.Sprintf("%c%c-%d%s-%d%s", 34 | getRandomLetterCode(), 35 | getRandomLetterCode(), 36 | len(salt), 37 | getRandomNumber(3), 38 | len(salt)/2, 39 | getRandomNumber(7), 40 | ) 41 | } 42 | 43 | // getRandomLetterCode generates a code point value for a capital letter. 44 | func getRandomLetterCode() uint32 { 45 | return 65 + uint32(rand.Intn(25)) 46 | } 47 | 48 | // getRandomNumber generates a string representation of a number with the requested number of digits. 49 | func getRandomNumber(digits int) string { 50 | str := "" 51 | for i := 0; i < digits; i++ { 52 | str = fmt.Sprintf("%s%d", str, rand.Intn(10)) 53 | } 54 | 55 | return str 56 | } 57 | --------------------------------------------------------------------------------