diff --git a/cmd/generic/main.go b/cmd/generic/main.go index 4890740..b61efc5 100644 --- a/cmd/generic/main.go +++ b/cmd/generic/main.go @@ -55,6 +55,9 @@ func main() { case "color": server = &faces.NewColorServer("ColorServer").BaseServer + case "ingress": + server = &faces.NewIngressServer("IngressServer").BaseServer + case "load": fmt.Printf("Running load generator") diff --git a/faces-chart/templates/_helpers.tpl b/faces-chart/templates/_helpers.tpl index 3089f4b..5cb18f9 100644 --- a/faces-chart/templates/_helpers.tpl +++ b/faces-chart/templates/_helpers.tpl @@ -92,6 +92,14 @@ {{- include "partials.select-errorFraction" (dict "source" .Values.face) -}} {{- end -}} +{{- define "partials.ingress-image" -}} + {{- include "partials.select-image" (dict "source" .Values.ingress "root" .) -}} +{{- end -}} + +{{- define "partials.ingress-imagePullPolicy" -}} + {{- include "partials.select-imagePullPolicy" (dict "source" .Values.ingress "root" .) -}} +{{- end -}} + {{- define "partials.color-image" -}} {{- include "partials.select-image" (dict "source" .Values.color "default" .Values.backend "root" .) -}} {{- end -}} diff --git a/faces-chart/templates/face.yaml b/faces-chart/templates/face.yaml index 73a513d..9b9bd95 100644 --- a/faces-chart/templates/face.yaml +++ b/faces-chart/templates/face.yaml @@ -1,15 +1,19 @@ +{{- $name := "face" -}} +{{- if .Values.ingress.enabled -}} + {{- $name = "cell" -}} +{{- end -}} --- apiVersion: v1 kind: Service metadata: - name: face + name: {{ $name }} namespace: {{ .Release.Namespace }} labels: - service: face + service: {{ $name }} spec: type: ClusterIP selector: - service: face + service: {{ $name }} ports: - port: 80 targetPort: http @@ -17,22 +21,22 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: face + name: {{ $name }} namespace: {{ .Release.Namespace }} labels: - service: face + service: {{ $name }} spec: replicas: 1 selector: matchLabels: - service: face + service: {{ $name }} template: metadata: labels: - service: face + service: {{ $name }} spec: containers: - - name: face + - name: {{ $name }} image: {{ include "partials.face-image" . }} imagePullPolicy: {{ include "partials.face-imagePullPolicy" . }} ports: diff --git a/faces-chart/templates/faces-gui.yaml b/faces-chart/templates/faces-gui.yaml index 42d777e..3b2fed6 100644 --- a/faces-chart/templates/faces-gui.yaml +++ b/faces-chart/templates/faces-gui.yaml @@ -1,3 +1,7 @@ +{{- $serviceType := .Values.gui.serviceType -}} +{{- if .Values.ingress.enabled -}} + {{- $serviceType = "LoadBalancer" -}} +{{- end -}} --- apiVersion: v1 kind: Service @@ -7,7 +11,7 @@ metadata: labels: service: faces-gui spec: - type: {{ .Values.gui.serviceType }} + type: {{ $serviceType }} selector: service: faces-gui ports: diff --git a/faces-chart/templates/ingress.yaml b/faces-chart/templates/ingress.yaml new file mode 100644 index 0000000..c330bae --- /dev/null +++ b/faces-chart/templates/ingress.yaml @@ -0,0 +1,56 @@ +{{- if .Values.ingress.enabled -}} +--- +apiVersion: v1 +kind: Service +metadata: + name: face + namespace: {{ .Release.Namespace }} + labels: + service: face +spec: + type: ClusterIP + selector: + service: face + ports: + - port: 80 + targetPort: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: face + namespace: {{ .Release.Namespace }} + labels: + service: face +spec: + replicas: 1 + selector: + matchLabels: + service: face + template: + metadata: + labels: + service: face + spec: + containers: + - name: face + image: {{ include "partials.ingress-image" . }} + imagePullPolicy: {{ include "partials.ingress-imagePullPolicy" . }} + ports: + - name: http + containerPort: 8000 + env: + - name: FACES_SERVICE + value: "ingress" + - name: USER_HEADER_NAME + value: {{ .Values.authHeader | quote }} + - name: CELL_SERVICE + value: {{ .Values.ingress.cellService | quote }} + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi +{{- end -}} diff --git a/faces-chart/values.yaml b/faces-chart/values.yaml index 19a8034..bb89a2f 100644 --- a/faces-chart/values.yaml +++ b/faces-chart/values.yaml @@ -27,6 +27,14 @@ face: errorFraction: "20" delayBuckets: "" +ingress: + enabled: False # If set to True, enables the ingress workload + image: "" # If set, overrides the imageName/imageTag pair + imageName: ghcr.io/buoyantio/faces-workload + imageTag: "" # If not set, uses the defaultImageTag + imagePullPolicy: "" # If not set, uses the default imagePullPolicy + cellService: "cell" # Override if desired + backend: image: "" # If set, overrides the imageName/imageTag pair imageName: ghcr.io/buoyantio/faces-workload diff --git a/pkg/faces/ingressserver.go b/pkg/faces/ingressserver.go new file mode 100644 index 0000000..267a600 --- /dev/null +++ b/pkg/faces/ingressserver.go @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2024 Buoyant Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 2022-2024 Buoyant Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package faces + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/BuoyantIO/faces-demo/v2/pkg/utils" +) + +type IngressServer struct { + BaseServer + faceService string +} + +type IngressResponse struct { + Smiley string `json:"smiley"` + Color string `json:"color"` + Rate string `json:"rate"` + Errors []string `json:"errors"` + Latency int64 `json:"latency"` +} + +func NewIngressServer(serverName string) *IngressServer { + srv := &IngressServer{ + BaseServer: BaseServer{ + Name: serverName, + }, + } + + srv.SetupFromEnvironment() + + srv.RegisterNormal("/center/", srv.ingressGetHandler) + srv.RegisterNormal("/edge/", srv.ingressGetHandler) + + return srv +} + +func (srv *IngressServer) SetupFromEnvironment() { + srv.BaseServer.SetupFromEnvironment() + + srv.faceService = utils.StringFromEnv("CELL_SERVICE", "cell") + + fmt.Printf("%s %s: faceService %v\n", time.Now().Format(time.RFC3339), srv.Name, srv.faceService) +} + +func (srv *IngressServer) ingressGetHandler(r *http.Request, rstat *BaseRequestStatus) *BaseServerResponse { + start := time.Now() + + response := BaseServerResponse{ + StatusCode: http.StatusOK, + } + + errors := []string{} + + smiley, _ := Smileys.Lookup(Defaults["smiley"]) + color := Colors.Lookup(Defaults["color"]) + rateStr := fmt.Sprintf("%.1f RPS", srv.CurrentRate()) + + if rstat.IsRateLimited() { + errors = append(errors, rstat.Message()) + smiley, _ = Smileys.Lookup(Defaults["smiley-ratelimit"]) + color = Colors.Lookup(Defaults["color-ratelimit"]) + } else { + url := fmt.Sprintf("http://%s%s", srv.faceService, r.URL.Path) + + if srv.debugEnabled { + fmt.Printf("%s %s: %s starting\n", time.Now().Format(time.RFC3339), srv.Name, url) + } + + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + errors = append(errors, fmt.Sprintf("failed to create request: %v", err)) + // No need to change smiley and color here. + } else { + // Copy headers from the original request + req.Header.Add(srv.userHeaderName, r.Header.Get(srv.userHeaderName)) + req.Header.Add("User-Agent", r.Header.Get("User-Agent")) + + resp, err := http.DefaultClient.Do(req) + + if err != nil { + errors = append(errors, fmt.Sprintf("request failed: %v", err)) + // No need to change smiley and color here. + } else { + defer resp.Body.Close() + + rcode := resp.StatusCode + + if srv.debugEnabled { + fmt.Printf("%s %s: %s returned %d\n", time.Now().Format(time.RFC3339), srv.Name, url, rcode) + } + + response.StatusCode = rcode + + body, err := io.ReadAll(resp.Body) + + if err != nil { + errors = append(errors, fmt.Sprintf("failed to read response body: %v", err)) + // No need to change smiley and color here. + } else { + var ingressResp IngressResponse + err := json.Unmarshal(body, &ingressResp) + + if err != nil { + errors = append(errors, fmt.Sprintf("failed to unmarshal response: %v", err)) + // No need to change smiley and color here. + } else { + smiley = ingressResp.Smiley + color = ingressResp.Color + rateStr = ingressResp.Rate + errors = ingressResp.Errors + } + } + } + } + } + + end := time.Now() + latency := end.Sub(start) + + response.Data = map[string]interface{}{ + "smiley": smiley, + "color": color, + "rate": rateStr, + "errors": errors, + "latency": latency.Milliseconds(), + } + + if srv.debugEnabled { + fmt.Printf("%s %s: %s, %s (%dms): %v\n", time.Now().Format(time.RFC3339), srv.Name, smiley, color, latency.Milliseconds(), errors) + } + + return &response +}