Skip to content

Commit

Permalink
Add flaky-service
Browse files Browse the repository at this point in the history
  • Loading branch information
Starefossen committed Jul 5, 2024
1 parent 51476c9 commit 710a862
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 0 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/flaky-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: flaky-service

on:
push:
branches: [ main ]
paths:
- .github/workflows/flaky-service.yaml
- flaky-service/**
pull_request:
branches: [ main ]
paths:
- .github/workflows/flaky-service.yaml
- flaky-service/**
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
REGISTRY: europe-north1-docker.pkg.dev/nais-io/nais/images

jobs:
build_push_sign:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: nais/platform-build-push-sign@main
id: image
with:
context: ./flaky-service
name: flaky-service
dockerfile: ./Dockerfile
google_service_account: gh-examples
push: ${{ github.actor != 'dependabot[bot]' }}
workload_identity_provider: ${{ secrets.NAIS_IO_WORKLOAD_IDENTITY_PROVIDER }}
outputs:
version: ${{ steps.image.outputs.version }}
rollout:
permissions:
id-token: write
name: Deploy to NAIS
needs: ["build_push_sign"]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: nais/deploy/actions/deploy@v2
env:
CLUSTER: dev-gcp
PRINT_PAYLOAD: "true"
RESOURCE: "./flaky-service/.nais/unleash.yaml,./flaky-service/.nais/app.yaml"
VAR: image=europe-north1-docker.pkg.dev/nais-io/nais/images/flaky-service:${{ needs.build_push_sign.outputs.version }},namespace=nais
22 changes: 22 additions & 0 deletions flaky-service/.nais/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
apiVersion: nais.io/v1alpha1
kind: Application
metadata:
name: flaky-service
namespace: {{ namespace }}
labels:
team: {{ namespace }}
spec:
image: {{ image }}
port: 8080
prometheus:
enabled: false
envFrom:
- secret: flaky-service-unleash-api-token
accessPolicy:
outbound:
external:
- host: nais-demo-unleash-api.nav.cloud.nais.io
replicas:
max: 1
min: 1
14 changes: 14 additions & 0 deletions flaky-service/.nais/unleash.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: unleash.nais.io/v1
kind: ApiToken
metadata:
name: flaky-service
namespace: {{ namespace }}
labels:
team: {{ namespace }}
spec:
unleashInstance:
apiVersion: unleash.nais.io/v1
kind: RemoteUnleash
name: nais-demo
secretName: flaky-service-unleash-api-token
environment: development
14 changes: 14 additions & 0 deletions flaky-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.22-alpine as builder
RUN apk add --no-cache git make curl build-base
ENV GOOS=linux
WORKDIR /src
COPY . /src/
RUN go mod download
RUN go build -o bin/flaky-service *.go

FROM alpine:3.18
RUN apk add --no-cache ca-certificates tzdata
RUN export PATH=$PATH:/app
WORKDIR /app
COPY --from=builder /src/bin/flaky-service /app/flaky-service
CMD ["/app/flaky-service"]
2 changes: 2 additions & 0 deletions flaky-service/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flaky-service:
go build -o bin/flaky-service *.go
22 changes: 22 additions & 0 deletions flaky-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Flaky Service

The Flaky Service is a simple service that aims to simulate flaky behavior in a service oriented architecture.

## Architecture

The service is a simple HTTP server that listens on port `8080` by default written in Go. It has a single endpoint that returns a `200 OK` response for good requests and a `500 Server Error` response for errors.

The flakiness is simulated by randomly returning a `500 Server Error` response for a percentage of requests. The flakiness limit can be adjusted using the Unleash feature toggle mentioned below.

## Endpoint

The service has only one endpoint:

### `/`

- Returns a `200 OK {"message": "hello, world"}` response for good requests.
- Returns a `500 Server Error {"error": "server error"}` response for errors.

## Adjusting Flakiness

The flakiness of the service can be adjusted using the Unleash feature toggle. The service checks the `flaky-service.flakiness-limit` feature toggle to determine the flakiness limit. If the feature toggle is not present, the service defaults to a flakiness limit of `50%`.
78 changes: 78 additions & 0 deletions flaky-service/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"fmt"
"log/slog"
"net/http"
"strconv"
"strings"

"github.com/Unleash/unleash-client-go/v3"
"github.com/Unleash/unleash-client-go/v3/api"
)

const (
AppName = "flaky-service"
unleashDefaultProject = "default"
unleashDefaultEnv = "development"
unleashDefaultType = "client"

toggleFlakinessLevelName = "flaky-service.flakiness-level"
toggleFlakinessLevelDefault = 50
)

type Config struct {
Server *ServerConfig
Unleash *UnleashConfig
}

type ServerConfig struct {
Port string `env:"PORT, default=8080"`
Host string `env:"HOST, default=0.0.0.0"`
}

type UnleashConfig struct {
ClientType string `env:"UNLEASH_SERVER_API_TYPE, default=client"`
Projects string `env:"UNLEASH_SERVER_API_PROJECTS, default=default"`
Env string `env:"UNLEASH_SERVER_API_ENV, default=development"`
Url string `env:"UNLEASH_SERVER_API_URL"`
Token string `env:"UNLEASH_SERVER_API_TOKEN"`
}

func (c *Config) UnleashInit() {
projects := strings.Split(c.Unleash.Projects, ",")
project := unleashDefaultProject
if len(projects) > 0 {
project = projects[0]
}

unleash.Initialize(
// unleash.WithListener(&unleash.DebugListener{}),
unleash.WithAppName(AppName),
unleash.WithEnvironment(c.Unleash.Env),
unleash.WithUrl(fmt.Sprintf("%s/api", c.Unleash.Url)),
unleash.WithProjectName(project),
unleash.WithCustomHeaders(http.Header{"Authorization": {fmt.Sprintf("Bearer %s", c.Unleash.Token)}}),
)
}

func (c *Config) FlakinessLevel() int {
variant := unleash.GetVariant(toggleFlakinessLevelName, unleash.WithVariantFallback(api.GetDefaultVariant()))

if variant == nil {
slog.Info("Failed to get variant", "featureToggleName", toggleFlakinessLevelName)
return toggleFlakinessLevelDefault
}

if variant.Payload.Type != "number" {
slog.Info("Invalid variant payload type", "featureToggleName", toggleFlakinessLevelName, "type", variant.Payload.Type, "value", variant.Payload.Value)
return toggleFlakinessLevelDefault
}

value, err := strconv.Atoi(variant.Payload.Value)
if err != nil {
slog.Info("Failed to parse variant value", "featureToggleName", toggleFlakinessLevelName, "type", variant.Payload.Type, "value", variant.Payload.Value, "error", err)
return toggleFlakinessLevelDefault
}
return value
}
18 changes: 18 additions & 0 deletions flaky-service/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module github.com/nais/examples/flaky-service

go 1.22.3

require github.com/joho/godotenv v1.5.1

require (
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Unleash/unleash-client-go v0.0.0-20190923201156-aae25c357956
github.com/Unleash/unleash-client-go/v3 v3.9.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sethvargo/go-envconfig v1.0.3 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.2.2 // indirect
github.com/twmb/murmur3 v1.1.5 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
)
24 changes: 24 additions & 0 deletions flaky-service/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Unleash/unleash-client-go v0.0.0-20190923201156-aae25c357956 h1:KW9P0PdJZyaM/A92zCNzwmLtlu2ZgtTlFRAXmQXwgXQ=
github.com/Unleash/unleash-client-go v0.0.0-20190923201156-aae25c357956/go.mod h1:89lPmFeGRU6Xv5kYZnN1ByXulB4vHW+UepUYSm8s8ws=
github.com/Unleash/unleash-client-go/v3 v3.9.2 h1:/Jl61G/kOx+1+MqPuMnC/JvJxdsf52ZDdJvCmXoA2ck=
github.com/Unleash/unleash-client-go/v3 v3.9.2/go.mod h1:jAf7F2WWpfJbfn1n8bZ74p7hkAhijrqH4TpWoT7kWLc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sethvargo/go-envconfig v1.0.3 h1:ZDxFGT1M7RPX0wgDOCdZMidrEB+NrayYr6fL0/+pk4I=
github.com/sethvargo/go-envconfig v1.0.3/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk=
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
gopkg.in/h2non/gock.v1 v1.0.10/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY=
55 changes: 55 additions & 0 deletions flaky-service/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"context"
"fmt"
"log"
"log/slog"
"net/http"

"github.com/joho/godotenv"
"github.com/sethvargo/go-envconfig"
"golang.org/x/exp/rand"
)

var c Config

func init() {
ctx := context.Background()

err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file", err)
}

if err := envconfig.Process(ctx, &c); err != nil {
log.Fatal("Error processing config:", err)
}

c.UnleashInit()
}

func main() {
slog.Info("Starting server", "flakinessLevel", c.Server.Host, "port", c.Server.Port)

// Start a simple HTTP server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

level := c.FlakinessLevel()
slog.Info("Flakiness level", "level", level)

w.Header().Set("X-Flakiness-Level", fmt.Sprintf("%d", level))

if rand.Intn(100) < level {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error": "Internal Server Error"}`))
return
}

w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "Hello, World!"}`))
})

log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", c.Server.Host, c.Server.Port), nil))
}

0 comments on commit 710a862

Please sign in to comment.