-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
51476c9
commit 710a862
Showing
10 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
flaky-service: | ||
go build -o bin/flaky-service *.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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%`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |