diff --git a/.github/workflows/docker_build_push.yaml b/.github/workflows/docker_build_push.yaml index 54ab865..dbbcd02 100644 --- a/.github/workflows/docker_build_push.yaml +++ b/.github/workflows/docker_build_push.yaml @@ -14,6 +14,7 @@ on: options: - httpserver - kafkaconsumer + - simulator jobs: docker_build: diff --git a/.github/workflows/helm_deploy_application.yaml b/.github/workflows/helm_deploy_application.yaml index 9942608..eebee8f 100644 --- a/.github/workflows/helm_deploy_application.yaml +++ b/.github/workflows/helm_deploy_application.yaml @@ -14,6 +14,7 @@ on: options: - httpserver - kafkaconsumer + - simulator jobs: helm_deploy: diff --git a/apps/golang/commons/otel/http/http_client_interceptor.go b/apps/golang/commons/otel/http/http_client_interceptor.go new file mode 100644 index 0000000..d0c582b --- /dev/null +++ b/apps/golang/commons/otel/http/http_client_interceptor.go @@ -0,0 +1,163 @@ +package http + +import ( + "context" + "net/http" + "time" + + semconv "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel/semconv/v1.24.0" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +type Opts struct { + Timeout time.Duration +} + +type OptFunc func(*Opts) + +func defaultOpts() *Opts { + return &Opts{ + Timeout: time.Duration(30 * time.Second), + } +} + +type HttpClient struct { + Opts *Opts + + client *http.Client + + tracer trace.Tracer + meter metric.Meter + propagator propagation.TextMapPropagator + + latency metric.Float64Histogram +} + +func New( + optFuncs ...OptFunc, +) *HttpClient { + + // Instantiate options with default values + opts := defaultOpts() + + // Apply external options + for _, f := range optFuncs { + f(opts) + } + + c := &http.Client{ + Timeout: opts.Timeout, + } + + // Instantiate trace provider + tracer := otel.GetTracerProvider().Tracer(semconv.HttpClientName) + + // Instantiate meter provider + meter := otel.GetMeterProvider().Meter(semconv.HttpClientName) + + // Instantiate propagator + propagator := otel.GetTextMapPropagator() + + // Create HTTP client latency histogram + latency, err := meter.Float64Histogram( + semconv.HttpClientLatencyName, + metric.WithUnit("ms"), + metric.WithDescription("Measures the duration of HTTP request handling"), + metric.WithExplicitBucketBoundaries(semconv.HttpExplicitBucketBoundaries...), + ) + if err != nil { + panic(err) + } + + return &HttpClient{ + client: c, + + tracer: tracer, + meter: meter, + propagator: propagator, + + latency: latency, + } +} + +// Configure timeout of the HTTP client +func WithTimeout(timeout time.Duration) OptFunc { + return func(opts *Opts) { + opts.Timeout = timeout + } +} + +func (c *HttpClient) Do( + ctx context.Context, + req *http.Request, + spanName string, +) ( + *http.Response, + error, +) { + requestStartTime := time.Now() + + // Parse HTTP attributes from the request for both span and metrics + spanAttrs, metricAttrs := c.getSpanAndMetricServerAttributes(req) + + // Create span options + spanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(spanAttrs...), + } + + // Start HTTP server span + ctx, span := c.tracer.Start(ctx, spanName, spanOpts...) + defer span.End() + + // Inject context into the HTTP headers + headers := http.Header{} + carrier := propagation.HeaderCarrier(headers) + otel.GetTextMapPropagator().Inject(ctx, carrier) + for k, v := range headers { + req.Header.Add(k, v[0]) + } + + res, err := c.client.Do(req) + + // Add HTTP status code to the attributes + span.SetAttributes(semconv.HttpResponseStatusCode.Int(res.StatusCode)) + metricAttrs = append(metricAttrs, semconv.HttpResponseStatusCode.Int(res.StatusCode)) + + // Create metric options + metricOpts := metric.WithAttributes(metricAttrs...) + + // Record server latency + elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) + c.latency.Record(ctx, elapsedTime, metricOpts) + + return res, err +} + +func (m *HttpClient) getSpanAndMetricServerAttributes( + r *http.Request, +) ( + []attribute.KeyValue, + []attribute.KeyValue, +) { + spanAttrs := semconv.WithHttpClientAttributes(r) + metricAttrs := make([]attribute.KeyValue, len(spanAttrs)) + copy(metricAttrs, spanAttrs) + + var url string + if r.URL != nil { + // Remove any username/password info that may be in the URL. + userinfo := r.URL.User + r.URL.User = nil + url = r.URL.String() + // Restore any username/password info that was removed. + r.URL.User = userinfo + } + spanAttrs = append(spanAttrs, semconv.HttpUrlFull.String(url)) + + return spanAttrs, metricAttrs +} diff --git a/apps/golang/commons/otel/http/http_client_interceptor_test.go b/apps/golang/commons/otel/http/http_client_interceptor_test.go new file mode 100644 index 0000000..67d0b08 --- /dev/null +++ b/apps/golang/commons/otel/http/http_client_interceptor_test.go @@ -0,0 +1,125 @@ +package http + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" + + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel" + semconv "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel/semconv/v1.24.0" +) + +func Test_CommonClientAttributesCreatedSuccessfully(t *testing.T) { + httpMethod := http.MethodPost + host := "localhost" + port := "8080" + userAgent := "test_agent" + + // Set headers + headers := http.Header{} + headers.Set("User-Agent", userAgent) + + req, err := http.NewRequest( + httpMethod, + fmt.Sprintf("http://%s:%s/", host, port), + nil, + ) + if err != nil { + t.Fatal(err) + } + req.Header = headers + + c := &HttpClient{} + spanAttrs, metricAttrs := c.getSpanAndMetricServerAttributes(req) + + // Check lengths of span and metric attributes + if len(spanAttrs) != len(metricAttrs) { + t.Error("Number of span and metric attributes are not the same!") + } + + for i, spanAttr := range spanAttrs { + metricAttr := metricAttrs[i] + + // Check span and metric attribute key and value + if spanAttr != metricAttr { + t.Error("Span and metric attribute are not the same!") + } + + if spanAttr.Key == semconv.NetworkProtocolVersionName && + spanAttr.Value.AsString() != "1.1" { + t.Errorf("%s is set incorrectly!", semconv.NetworkProtocolVersionName) + } + + if spanAttr.Key == semconv.HttpMethodKeyName && + spanAttr.Value.AsString() != httpMethod { + t.Errorf("%s is set incorrectly!", semconv.HttpMethodKeyName) + } + + if spanAttr.Key == semconv.ServerAddressName && + spanAttr.Value.AsString() != host { + t.Errorf("%s is set incorrectly!", semconv.ServerAddressName) + } + + if spanAttr.Key == semconv.ServerPortName { + portAsInt, _ := strconv.ParseInt(port, 10, 64) + if spanAttr.Value.AsInt64() != portAsInt { + t.Errorf("%s is set incorrectly!", semconv.ServerPortName) + } + } + + if spanAttr.Key == semconv.HttpUserAgentOriginalName && + spanAttr.Value.AsString() != userAgent { + t.Errorf("%s is set incorrectly!", semconv.HttpUserAgentOriginalName) + } + } +} + +func Test_InjectTraceContextCorrectly(t *testing.T) { + + mockCtx := context.Background() + + // Create tracer provider + tp := otel.NewTraceProvider(mockCtx) + defer otel.ShutdownTraceProvider(mockCtx, tp) + + // prop := propagation.TraceContext{} + + httpMethod := http.MethodPost + httpStatusCode := http.StatusAccepted + + // Createa a mock HTTP server + mockServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(httpStatusCode) + w.Write([]byte("Test")) + })) + defer mockServer.Close() + + httpClient := New( + WithTimeout(time.Duration(10 * time.Second)), + ) + + req, err := http.NewRequest( + httpMethod, + mockServer.URL, + nil, + ) + if err != nil { + t.Fatal(err) + } + + res, _ := httpClient.Do(mockCtx, req, fmt.Sprintf("HTTP %s", req.Method)) + if res.StatusCode != httpStatusCode { + t.Error("HTTP status code is not the as given.") + } + + traceparent := req.Header.Get("Traceparent") + if len(traceparent) == 0 { + t.Error("Span context is not set to the outgoing request.") + } +} diff --git a/apps/golang/commons/otel/http/http_interceptor.go b/apps/golang/commons/otel/http/http_server_interceptor.go similarity index 100% rename from apps/golang/commons/otel/http/http_interceptor.go rename to apps/golang/commons/otel/http/http_server_interceptor.go diff --git a/apps/golang/commons/otel/http/http_interceptor_test.go b/apps/golang/commons/otel/http/http_server_interceptor_test.go similarity index 98% rename from apps/golang/commons/otel/http/http_interceptor_test.go rename to apps/golang/commons/otel/http/http_server_interceptor_test.go index 11efdfc..aeab93e 100644 --- a/apps/golang/commons/otel/http/http_interceptor_test.go +++ b/apps/golang/commons/otel/http/http_server_interceptor_test.go @@ -13,7 +13,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -func Test_CommonAttributesCreatedSuccessfully(t *testing.T) { +func Test_CommonServerAttributesCreatedSuccessfully(t *testing.T) { httpMethod := http.MethodPost host := "localhost" port := "8080" diff --git a/apps/golang/commons/otel/kafka/kafka_interceptor.go b/apps/golang/commons/otel/kafka/kafka_consumer_interceptor.go similarity index 98% rename from apps/golang/commons/otel/kafka/kafka_interceptor.go rename to apps/golang/commons/otel/kafka/kafka_consumer_interceptor.go index e6b255e..3ef4d9e 100644 --- a/apps/golang/commons/otel/kafka/kafka_interceptor.go +++ b/apps/golang/commons/otel/kafka/kafka_consumer_interceptor.go @@ -21,7 +21,7 @@ type KafkaConsumer struct { latency metric.Float64Histogram } -func New() *KafkaConsumer { +func NewKafkaConsumer() *KafkaConsumer { // Instantiate trace provider tracer := otel.GetTracerProvider().Tracer(semconv.KafkaConsumerName) diff --git a/apps/golang/commons/otel/kafka/kafka_producer_interceptor.go b/apps/golang/commons/otel/kafka/kafka_producer_interceptor.go new file mode 100644 index 0000000..80ea2bc --- /dev/null +++ b/apps/golang/commons/otel/kafka/kafka_producer_interceptor.go @@ -0,0 +1,105 @@ +package kafka + +import ( + "context" + "fmt" + "time" + + "github.com/IBM/sarama" + semconv "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel/semconv/v1.24.0" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +type KafkaProducer struct { + producer sarama.AsyncProducer + + tracer trace.Tracer + meter metric.Meter + propagator propagation.TextMapPropagator + + latency metric.Float64Histogram +} + +func NewKafkaProducer( + producer sarama.AsyncProducer, +) *KafkaProducer { + + // Instantiate trace provider + tracer := otel.GetTracerProvider().Tracer(semconv.KafkaProducerName) + + // Instantiate meter provider + meter := otel.GetMeterProvider().Meter(semconv.KafkaProducerName) + + // Instantiate propagator + propagator := otel.GetTextMapPropagator() + + // Create HTTP client latency histogram + latency, err := meter.Float64Histogram( + semconv.MessagingProducerLatencyName, + metric.WithUnit("ms"), + metric.WithDescription("Measures the duration of publish operation"), + metric.WithExplicitBucketBoundaries(semconv.MessagingExplicitBucketBoundaries...), + ) + if err != nil { + panic(err) + } + + return &KafkaProducer{ + producer: producer, + + tracer: tracer, + meter: meter, + propagator: propagator, + + latency: latency, + } +} + +func (k *KafkaProducer) Publish( + ctx context.Context, + msg *sarama.ProducerMessage, +) { + + produceStartTime := time.Now() + + // Inject tracing info into message + span := k.createProducerSpan(ctx, msg) + defer span.End() + + // Publish message + k.producer.Input() <- msg + <-k.producer.Successes() + + // Record producer latency + elapsedTime := float64(time.Since(produceStartTime)) / float64(time.Millisecond) + k.latency.Record(ctx, elapsedTime, + metric.WithAttributes( + semconv.WithMessagingKafkaProducerAttributes(msg)..., + )) +} + +func (k *KafkaProducer) createProducerSpan( + ctx context.Context, + msg *sarama.ProducerMessage, +) trace.Span { + spanAttrs := semconv.WithMessagingKafkaProducerAttributes(msg) + spanContext, span := k.tracer.Start( + ctx, + fmt.Sprintf("%s publish", msg.Topic), + trace.WithSpanKind(trace.SpanKindProducer), + trace.WithAttributes(spanAttrs...), + ) + + carrier := propagation.MapCarrier{} + propagator := otel.GetTextMapPropagator() + propagator.Inject(spanContext, carrier) + + for key, value := range carrier { + msg.Headers = append(msg.Headers, sarama.RecordHeader{Key: []byte(key), Value: []byte(value)}) + } + + return span +} diff --git a/apps/golang/commons/otel/semconv/v1.24.0/semconv.go b/apps/golang/commons/otel/semconv/v1.24.0/semconv.go index 8303cd6..78930a1 100644 --- a/apps/golang/commons/otel/semconv/v1.24.0/semconv.go +++ b/apps/golang/commons/otel/semconv/v1.24.0/semconv.go @@ -41,10 +41,17 @@ const ( HttpInterceptorName = "http_interceptor" HttpServerLatencyName = "http.server.request.duration" - HttpMethodKeyName = "http.request.method" - HttpMethodKey = attribute.Key(HttpMethodKeyName) - HttpSchemeKeyName = "url.scheme" - HttpSchemeKey = attribute.Key(HttpSchemeKeyName) + HttpClientName = "http_client" + HttpClientLatencyName = "http.client.request.duration" + + HttpMethodKeyName = "http.request.method" + HttpMethodKey = attribute.Key(HttpMethodKeyName) + HttpSchemeKeyName = "url.scheme" + HttpSchemeKey = attribute.Key(HttpSchemeKeyName) + HttpUserAgentOriginalName = "user_agent.original" + HttpUserAgentOriginal = attribute.Key(HttpUserAgentOriginalName) + HttpUrlFullName = "url.full" + HttpUrlFull = attribute.Key(HttpUrlFullName) HttpResponseStatusCodeName = "http.response.status_code" HttpResponseStatusCode = attribute.Key(HttpResponseStatusCodeName) @@ -69,6 +76,60 @@ var ( } ) +func WithHttpClientAttributes( + req *http.Request, +) []attribute.KeyValue { + + numAttributes := 4 // Method, scheme, proto & server address + + // Get user agent + userAgent := req.UserAgent() + if userAgent != "" { + numAttributes++ + } + + // Get server address & serverPort + serverAddress, serverPort := splitAddressAndPort(req.Host) + if serverPort > 0 { + numAttributes++ + } + + // Get client address & port + clientAddress, clientPort := splitAddressAndPort(req.RemoteAddr) + if clientPort > 0 { + numAttributes++ + } + + // Create attributes array + attrs := make([]attribute.KeyValue, 0, numAttributes) + + // Method, scheme & protocol version + attrs = append(attrs, httpMethod(req.Method)) + attrs = append(attrs, httpScheme(req.TLS != nil)) + attrs = append(attrs, httpNetworkProtocolVersion(req.Proto)) + + // User agent + if userAgent != "" { + attrs = append(attrs, HttpUserAgentOriginal.String(userAgent)) + } + + // Server address & port + attrs = append(attrs, ServerAddress.String(serverAddress)) + if serverPort > 0 { + attrs = append(attrs, ServerPort.Int(serverPort)) + } + + // Client address & port + if clientAddress != "" { + attrs = append(attrs, ClientAddress.String(clientAddress)) + if serverPort > 0 { + attrs = append(attrs, ClientPort.Int(clientPort)) + } + } + + return attrs +} + func WithHttpServerAttributes( req *http.Request, ) []attribute.KeyValue { @@ -212,8 +273,10 @@ const ( // KAFKA // https://github.com/open-telemetry/semantic-conventions/tree/v1.24.0/docs/messaging const ( + KafkaProducerName = "kafka_producer" KafkaConsumerName = "kafka_consumer" + MessagingProducerLatencyName = "messaging.publish.duration" MessagingConsumerLatencyName = "messaging.receive.duration" MessagingSystemName = "messaging.system" @@ -253,6 +316,24 @@ var ( } ) +func WithMessagingKafkaProducerAttributes( + msg *sarama.ProducerMessage, +) []attribute.KeyValue { + + numAttributes := 4 // Operation, system, destination & partition + + // Create attributes array + attrs := make([]attribute.KeyValue, 0, numAttributes) + + // Method, scheme & protocol version + attrs = append(attrs, MessagingSystem.String("kafka")) + attrs = append(attrs, MessagingOperation.String("publish")) + attrs = append(attrs, MessagingDestinationName.String(msg.Topic)) + attrs = append(attrs, MessagingKafkaDestinationPartition.Int(int(msg.Partition))) + + return attrs +} + func WithMessagingKafkaConsumerAttributes( msg *sarama.ConsumerMessage, consumerGroup string, diff --git a/apps/golang/kafkaconsumer/consumer/consumer.go b/apps/golang/kafkaconsumer/consumer/consumer.go index 90aedc3..d28f565 100644 --- a/apps/golang/kafkaconsumer/consumer/consumer.go +++ b/apps/golang/kafkaconsumer/consumer/consumer.go @@ -115,7 +115,7 @@ func (k *KafkaConsumer) StartConsumerGroup( return err } - otelconsumer := otelkafka.New() + otelconsumer := otelkafka.NewKafkaConsumer() handler := groupHandler{ ready: make(chan bool), Opts: k.Opts, diff --git a/apps/golang/kafkaconsumer/go.mod b/apps/golang/kafkaconsumer/go.mod index a7a30d6..2dae799 100644 --- a/apps/golang/kafkaconsumer/go.mod +++ b/apps/golang/kafkaconsumer/go.mod @@ -9,7 +9,6 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/sirupsen/logrus v1.9.3 github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons v0.0.0 - go.opentelemetry.io/contrib/instrumentation/runtime v0.47.0 go.opentelemetry.io/otel v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 ) @@ -36,6 +35,7 @@ require ( github.com/klauspost/compress v1.17.5 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.47.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect diff --git a/apps/golang/kafkaconsumer/main.go b/apps/golang/kafkaconsumer/main.go index fec8b61..d8cba71 100644 --- a/apps/golang/kafkaconsumer/main.go +++ b/apps/golang/kafkaconsumer/main.go @@ -4,7 +4,6 @@ import ( "context" "os" "os/signal" - "time" _ "github.com/go-sql-driver/mysql" "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/logger" @@ -12,7 +11,6 @@ import ( "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel" "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/kafkaconsumer/config" "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/kafkaconsumer/consumer" - "go.opentelemetry.io/contrib/instrumentation/runtime" ) func main() { @@ -35,11 +33,8 @@ func main() { mp := otel.NewMetricProvider(ctx) defer otel.ShutdownMetricProvider(ctx, mp) - // Start runtime metric collection - err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second)) - if err != nil { - panic(err) - } + // Collect runtime metrics + otel.StartCollectingRuntimeMetrics() // Instantiate MySQL database db := mysql.New( diff --git a/apps/golang/simulator/config/config.go b/apps/golang/simulator/config/config.go new file mode 100644 index 0000000..4f413b9 --- /dev/null +++ b/apps/golang/simulator/config/config.go @@ -0,0 +1,45 @@ +package config + +import "os" + +type SimulatorConfig struct { + + // App name + ServiceName string + + // HTTP server + HttpserverRequestInterval string + HttpserverEndpoint string + HttpserverPort string + + // Kafka producer + KafkaRequestInterval string + KafkaBrokerAddress string + KafkaTopic string + + // Users + Users []string +} + +// Creates new config object by parsing environment variables +func NewConfig() *SimulatorConfig { + return &SimulatorConfig{ + ServiceName: os.Getenv("OTEL_SERVICE_NAME"), + + HttpserverRequestInterval: os.Getenv("HTTP_SERVER_REQUEST_INTERVAL"), + HttpserverEndpoint: os.Getenv("HTTP_SERVER_ENDPOINT"), + HttpserverPort: os.Getenv("HTTP_SERVER_PORT"), + + KafkaRequestInterval: os.Getenv("KAFKA_REQUEST_INTERVAL"), + KafkaBrokerAddress: os.Getenv("KAFKA_BROKER_ADDRESS"), + KafkaTopic: os.Getenv("KAFKA_TOPIC"), + + Users: []string{ + "elon", + "jeff", + "warren", + "bill", + "mark", + }, + } +} diff --git a/apps/golang/simulator/go.mod b/apps/golang/simulator/go.mod new file mode 100644 index 0000000..2b6f9d1 --- /dev/null +++ b/apps/golang/simulator/go.mod @@ -0,0 +1,53 @@ +module github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/simulator + +go 1.21 + +replace github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons => ../commons + +require ( + github.com/IBM/sarama v1.42.1 + github.com/sirupsen/logrus v1.9.0 + github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons v0.0.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/eapache/go-resiliency v1.5.0 // indirect + github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/klauspost/compress v1.17.5 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.47.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/sdk v1.22.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/apps/golang/simulator/go.sum b/apps/golang/simulator/go.sum new file mode 100644 index 0000000..961dd50 --- /dev/null +++ b/apps/golang/simulator/go.sum @@ -0,0 +1,153 @@ +github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= +github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/eapache/go-resiliency v1.5.0 h1:dRsaR00whmQD+SgVKlq/vCRFNgtEb5yppyeVos3Yce0= +github.com/eapache/go-resiliency v1.5.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E= +github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +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/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.47.0 h1:fQChVLLl1h/42YGVoikLZ8yBrf1VG4DSEJ1zDnMfIog= +go.opentelemetry.io/contrib/instrumentation/runtime v0.47.0/go.mod h1:oEBtteRW7mKJc+yAKRuEu0xk5wyPUKpm41/bDM4ZKeY= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 h1:tfil6di0PoNV7FZdsCS7A5izZoVVQ7AuXtyekbOpG/I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0/go.mod h1:AKFZIEPOnqB00P63bTjOiah4ZTaRzl1TKwUWpZdYUHI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= +go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/apps/golang/simulator/httpclient/httpclient.go b/apps/golang/simulator/httpclient/httpclient.go new file mode 100644 index 0000000..e96d1d7 --- /dev/null +++ b/apps/golang/simulator/httpclient/httpclient.go @@ -0,0 +1,223 @@ +package httpclient + +import ( + "context" + "errors" + "fmt" + "io" + "math/rand" + "net/http" + "strconv" + "time" + + "github.com/sirupsen/logrus" + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/logger" + + otelhttp "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel/http" +) + +var ( + randomErrors = map[int]string{ + 1: "databaseConnectionError", + 2: "tableDoesNotExistError", + 3: "preprocessingException", + 4: "schemaNotFoundInCacheWarning", + } +) + +type Opts struct { + ServiceName string + RequestInterval int64 + ServerEndpoint string + ServerPort string +} + +type OptFunc func(*Opts) + +func defaultOpts() *Opts { + return &Opts{ + RequestInterval: 2000, + ServerEndpoint: "httpserver", + ServerPort: "8080", + } +} + +type HttpServerSimulator struct { + Opts *Opts + Client *otelhttp.HttpClient + Randomizer *rand.Rand +} + +// Create an HTTP server simulator instance +func New( + optFuncs ...OptFunc, +) *HttpServerSimulator { + + // Instantiate options with default values + opts := defaultOpts() + + // Apply external options + for _, f := range optFuncs { + f(opts) + } + + httpClient := otelhttp.New( + otelhttp.WithTimeout(time.Duration(10 * time.Second)), + ) + + randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) + + return &HttpServerSimulator{ + Opts: opts, + Client: httpClient, + Randomizer: randomizer, + } +} + +// Configure service name of simulator +func WithServiceName(serviceName string) OptFunc { + return func(opts *Opts) { + opts.ServiceName = serviceName + } +} + +// Configure HTTP server request interval +func WithRequestInterval(requestInterval string) OptFunc { + interval, err := strconv.ParseInt(requestInterval, 10, 64) + if err != nil { + panic(err.Error()) + } + return func(opts *Opts) { + opts.RequestInterval = interval + } +} + +// Configure HTTP server endpoint +func WithServerEndpoint(serverEndpoint string) OptFunc { + return func(opts *Opts) { + opts.ServerEndpoint = serverEndpoint + } +} + +// Configure HTTP server port +func WithServerPort(serverPort string) OptFunc { + return func(opts *Opts) { + opts.ServerPort = serverPort + } +} + +// Starts simulating HTTP server +func (h *HttpServerSimulator) Simulate( + users []string, +) { + + // LIST simulator + go func() { + for { + + // Make request after each interval + time.Sleep(time.Duration(h.Opts.RequestInterval) * time.Millisecond) + + // List + h.performHttpCall( + context.Background(), + http.MethodGet, + users[h.Randomizer.Intn(len(users))], + h.causeRandomError(), + ) + } + }() + + // DELETE simulator + go func() { + for { + + // Make request after each interval * 4 + time.Sleep(4 * time.Duration(h.Opts.RequestInterval) * time.Millisecond) + + // Delete + h.performHttpCall( + context.Background(), + http.MethodDelete, + users[h.Randomizer.Intn(len(users))], + h.causeRandomError(), + ) + } + }() +} + +// Puts necessary request parameters into a map in order to +// cause a random error +func (h *HttpServerSimulator) causeRandomError() map[string]string { + + randomNum := h.Randomizer.Intn(15) + reqParams := map[string]string{} + + if randomNum == 1 || randomNum == 2 || randomNum == 3 || randomNum == 4 { + reqParams[randomErrors[randomNum]] = "true" + } + + return reqParams +} + +// Performs the HTTP call to the HTTP server +func (h *HttpServerSimulator) performHttpCall( + ctx context.Context, + httpMethod string, + user string, + reqParams map[string]string, +) error { + + logger.Log(logrus.InfoLevel, ctx, user, "Preparing HTTP call...") + + // Create HTTP request with trace context + req, err := http.NewRequest( + httpMethod, + "http://"+h.Opts.ServerEndpoint+":"+h.Opts.ServerPort+"/api", + nil, + ) + if err != nil { + logger.Log(logrus.ErrorLevel, ctx, user, err.Error()) + return err + } + + // Add headers + req.Header.Add("Content-Type", "application/json") + req.Header.Add("X-User-ID", user) + + // Add request params + qps := req.URL.Query() + for k, v := range reqParams { + qps.Add(k, v) + } + if len(qps) > 0 { + req.URL.RawQuery = qps.Encode() + logger.Log(logrus.InfoLevel, ctx, user, "Request params->"+req.URL.RawQuery) + } + logger.Log(logrus.InfoLevel, ctx, user, "HTTP call is prepared.") + + // Perform HTTP request + logger.Log(logrus.InfoLevel, ctx, user, "Performing HTTP call") + res, err := h.Client.Do(ctx, req, fmt.Sprintf("HTTP %s", req.Method)) + if err != nil { + logger.Log(logrus.ErrorLevel, ctx, user, err.Error()) + return err + } + defer res.Body.Close() + + // Read HTTP response + resBody, err := io.ReadAll(res.Body) + if err != nil { + logger.Log(logrus.ErrorLevel, ctx, user, err.Error()) + return err + } + + // Check status code + if res.StatusCode != http.StatusOK { + logger.Log(logrus.ErrorLevel, ctx, user, string(resBody)) + return errors.New("call to donald returned not ok status") + } + + logger.Log(logrus.InfoLevel, ctx, user, "HTTP call is performed successfully.") + return nil +} diff --git a/apps/golang/simulator/kafkaproducer/kafkaproducer.go b/apps/golang/simulator/kafkaproducer/kafkaproducer.go new file mode 100644 index 0000000..5ce0b86 --- /dev/null +++ b/apps/golang/simulator/kafkaproducer/kafkaproducer.go @@ -0,0 +1,165 @@ +package kafkaproducer + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "time" + + "github.com/IBM/sarama" + "github.com/sirupsen/logrus" + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/logger" + + otelkafka "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel/kafka" +) + +type Opts struct { + ServiceName string + RequestInterval int64 + BrokerAddress string + BrokerTopic string +} + +type OptFunc func(*Opts) + +func defaultOpts() *Opts { + return &Opts{ + RequestInterval: 2000, + BrokerAddress: "kafka", + BrokerTopic: "otel", + } +} + +type KafkaConsumerSimulator struct { + Opts *Opts + Randomizer *rand.Rand +} + +// Create an kafka consumer simulator instance +func New( + optFuncs ...OptFunc, +) *KafkaConsumerSimulator { + + // Instantiate options with default values + opts := defaultOpts() + + // Apply external options + for _, f := range optFuncs { + f(opts) + } + + randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) + + return &KafkaConsumerSimulator{ + Opts: opts, + Randomizer: randomizer, + } +} + +// Configure service name of simulator +func WithServiceName(serviceName string) OptFunc { + return func(opts *Opts) { + opts.ServiceName = serviceName + } +} + +// Configure Kafka request interval +func WithRequestInterval(requestInterval string) OptFunc { + interval, err := strconv.ParseInt(requestInterval, 10, 64) + if err != nil { + panic(err.Error()) + } + return func(opts *Opts) { + opts.RequestInterval = interval + } +} + +// Configure Kafka broker address +func WithBrokerAddress(address string) OptFunc { + return func(opts *Opts) { + opts.BrokerAddress = address + } +} + +// Configure Kafka broker topic +func WithBrokerTopic(topic string) OptFunc { + return func(opts *Opts) { + opts.BrokerTopic = topic + } +} + +// Starts simulating Kafka consumer +func (k *KafkaConsumerSimulator) Simulate( + users []string, +) { + + // Create producer + producer := k.createKafkaProducer() + + // Wrap OTel around the producer + otelproducer := otelkafka.NewKafkaProducer(producer) + + // Publish messages + go k.publishMessages(otelproducer, users) +} + +// Creates the Kafka producer +func (k *KafkaConsumerSimulator) createKafkaProducer() sarama.AsyncProducer { + + // Create config + saramaConfig := sarama.NewConfig() + saramaConfig.Version = sarama.V3_0_0_0 + saramaConfig.Producer.Return.Successes = true + saramaConfig.Producer.Partitioner = sarama.NewRoundRobinPartitioner + + // Create producer + producer, err := sarama.NewAsyncProducer( + []string{k.Opts.BrokerAddress}, + saramaConfig, + ) + if err != nil { + panic(err) + } + + // Print errors if message publishing goes wrong + go func() { + for err := range producer.Errors() { + fmt.Println("Failed to write message: " + err.Error()) + } + }() + + return producer +} + +// Publish messages to topic +func (k *KafkaConsumerSimulator) publishMessages( + otelproducer *otelkafka.KafkaProducer, + users []string, +) { + + // Keep publishing messages + for { + func() { + // Make request after each interval + time.Sleep(time.Duration(k.Opts.RequestInterval) * time.Millisecond) + + // Get a random user + user := users[k.Randomizer.Intn(len(users))] + + // Create message + msg := sarama.ProducerMessage{ + Topic: k.Opts.BrokerTopic, + Value: sarama.ByteEncoder([]byte(user)), + } + + // Inject tracing info into message + ctx := context.Background() + + // Publish message + logger.Log(logrus.InfoLevel, ctx, user, "Publishing message...") + otelproducer.Publish(ctx, &msg) + logger.Log(logrus.InfoLevel, ctx, user, "Message published successfully.") + }() + } +} diff --git a/apps/golang/simulator/main.go b/apps/golang/simulator/main.go new file mode 100644 index 0000000..fcaabee --- /dev/null +++ b/apps/golang/simulator/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "os" + "os/signal" + + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/logger" + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/commons/otel" + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/simulator/config" + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/simulator/httpclient" + "github.com/utr1903/opentelemetry-kubernetes-demo/apps/golang/simulator/kafkaproducer" +) + +func main() { + // Get context + ctx := context.Background() + + // Create new config + cfg := config.NewConfig() + + // Initialize logger + logger.NewLogger(cfg.ServiceName) + + // Create tracer provider + tp := otel.NewTraceProvider(ctx) + defer otel.ShutdownTraceProvider(ctx, tp) + + // Create metric provider + mp := otel.NewMetricProvider(ctx) + defer otel.ShutdownMetricProvider(ctx, mp) + + // Collect runtime metrics + otel.StartCollectingRuntimeMetrics() + + // Simulate + go simulateHttpServer(cfg) + go simulateKafkaConsumer(cfg) + + // Wait for signal to shutdown the simulator + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + <-ctx.Done() +} + +func simulateHttpServer( + cfg *config.SimulatorConfig, +) { + // Instantiate HTTP server simulator + httpserverSimulator := httpclient.New( + httpclient.WithServiceName(cfg.ServiceName), + httpclient.WithRequestInterval(cfg.HttpserverRequestInterval), + httpclient.WithServerEndpoint(cfg.HttpserverEndpoint), + httpclient.WithServerPort(cfg.HttpserverPort), + ) + + // Simulate + httpserverSimulator.Simulate(cfg.Users) +} + +func simulateKafkaConsumer( + cfg *config.SimulatorConfig, +) { + // Instantiate Kafka consumer simulator + kafkaConsumerSimulator := kafkaproducer.New( + kafkaproducer.WithServiceName(cfg.ServiceName), + kafkaproducer.WithRequestInterval(cfg.KafkaRequestInterval), + kafkaproducer.WithBrokerAddress(cfg.KafkaBrokerAddress), + kafkaproducer.WithBrokerTopic(cfg.KafkaTopic), + ) + + // Simulate + kafkaConsumerSimulator.Simulate(cfg.Users) +} diff --git a/infra/helm/simulator/README.md b/infra/helm/simulator/README.md new file mode 100644 index 0000000..c28d704 --- /dev/null +++ b/infra/helm/simulator/README.md @@ -0,0 +1,3 @@ +# Simulator + +To be implemented... \ No newline at end of file diff --git a/infra/helm/simulator/chart/.helmignore b/infra/helm/simulator/chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/infra/helm/simulator/chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/infra/helm/simulator/chart/Chart.yaml b/infra/helm/simulator/chart/Chart.yaml new file mode 100644 index 0000000..cb94511 --- /dev/null +++ b/infra/helm/simulator/chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: simulator +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/infra/helm/simulator/chart/templates/deployment.yaml b/infra/helm/simulator/chart/templates/deployment.yaml new file mode 100644 index 0000000..93f8990 --- /dev/null +++ b/infra/helm/simulator/chart/templates/deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.name }} + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ .Values.name }} + template: + metadata: + labels: + app: {{ .Values.name }} + spec: + containers: + - name: {{ .Values.name }} + image: {{ .Values.imageName }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + env: + - name: K8S_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: APP_NAME + value: {{ .Values.name }} + - name: HTTP_SERVER_REQUEST_INTERVAL + value: "{{ .Values.httpserver.requestInterval }}" + - name: HTTP_SERVER_ENDPOINT + value: {{ .Values.httpserver.endpoint }} + - name: HTTP_SERVER_PORT + value: "{{ .Values.httpserver.port }}" + - name: KAFKA_REQUEST_INTERVAL + value: "{{ .Values.kafka.requestInterval }}" + - name: KAFKA_BROKER_ADDRESS + value: {{ .Values.kafka.address }} + - name: KAFKA_TOPIC + value: {{ .Values.kafka.topic }} + - name: OTEL_SERVICE_NAME + value: {{ .Values.name }} + - name: OTEL_RESOURCE_ATTRIBUTES + value: service.name=$(OTEL_SERVICE_NAME),service.instance.id=$(K8S_POD_NAME) + - name: OTEL_EXPORTER_TYPE + value: {{ .Values.otel.exporter }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ .Values.otlp.endpoint }} + - name: OTEL_EXPORTER_OTLP_HEADERS + value: {{ .Values.otlp.headers }} + ports: + - protocol: TCP + containerPort: {{ .Values.port }} + resources: + requests: + cpu: {{ .Values.resources.requests.cpu }} + memory: {{ .Values.resources.requests.memory }} + limits: + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.memory }} diff --git a/infra/helm/simulator/chart/templates/service.yaml b/infra/helm/simulator/chart/templates/service.yaml new file mode 100644 index 0000000..b74fb61 --- /dev/null +++ b/infra/helm/simulator/chart/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Values.name }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.port }} + targetPort: {{ .Values.port }} + protocol: TCP + name: http + selector: + app: {{ .Values.name }} diff --git a/infra/helm/simulator/chart/values.yaml b/infra/helm/simulator/chart/values.yaml new file mode 100644 index 0000000..e2610f7 --- /dev/null +++ b/infra/helm/simulator/chart/values.yaml @@ -0,0 +1,54 @@ +### Variables + +# Name +name: simulator + +# Port +port: 8080 + +# Replicas +replicas: 1 + +# Resources +resources: + # Requests + requests: + # CPU + cpu: 20m + # Memory + memory: 50Mi + # Limits + limits: + # CPU + cpu: 800m + # Memory + memory: 1000Mi + +# OTel +otel: + exporter: "stdout" + +# OTLP +otlp: + # Endpoint + endpoint: "https://otlp.nr-data.net:4317" + # Headers + headers: "" + +# Parameters for HTTP server +httpserver: + # Interval between each request + requestInterval: "2000" + # Endpoint of HTTP server + endpoint: "httpserver.otel.svc.cluster.local" + # Port of HTTP server + port: "8080" + +# Kafka +kafka: + # Interval between each request + requestInterval: "1000" + # Address + address: "kafka.otel.svc.cluster.local:9092" + # Topic + topic: "otel" diff --git a/infra/helm/simulator/deploy.sh b/infra/helm/simulator/deploy.sh new file mode 100644 index 0000000..e5735ed --- /dev/null +++ b/infra/helm/simulator/deploy.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Get commandline arguments +while (( "$#" )); do + case "$1" in + --project) + project="${2}" + shift + ;; + --instance) + instance="${2}" + shift + ;; + --application) + application="${2}" + shift + ;; + --language) + language="${2}" + shift + ;; + *) + shift + ;; + esac +done + +### Check input + +# Project +if [[ $project == "" ]]; then + echo -e "Project [--project] is not provided!\n" + exit 1 +fi + +# Instance +if [[ $instance == "" ]]; then + echo -e "Instance [--instance] is not provided!\n" + exit 1 +fi + +# Application +if [[ $application == "" ]]; then + echo -e "Application [--application] is not provided!\n" + exit 1 +fi + +# Language +if [[ $language == "" ]]; then + echo -e "Language [--language] is not provided!\n" + exit 1 +fi + +### Set variables + +# kafka +declare -A kafka +kafka["name"]="kafka" +kafka["namespace"]="ops" +kafka["topic"]="${language}" + +# otelcollectors +declare -A otelcollectors +otelcollectors["name"]="nrotelk8s" +otelcollectors["namespace"]="ops" +otelcollectors["endpoint"]="http://${otelcollectors[name]}-dep-rec-collector-headless.${otelcollectors[namespace]}.svc.cluster.local:4317" + +# httpserver +declare -A httpserver +httpserver["name"]="httpserver" +httpserver["namespace"]="${language}" +httpserver["port"]=8080 + +# simulator +declare -A simulator +simulator["name"]="simulator" +simulator["imageName"]="ghcr.io/utr1903/${project}-${simulator[name]}-${language}:latest" +simulator["namespace"]="${language}" +simulator["replicas"]=3 +simulator["port"]=8080 +simulator["httpInterval"]=2000 +simulator["kafkaInterval"]=1000 + +################### +### Deploy Helm ### +################### + +# simulator +helm upgrade ${simulator[name]} \ + --install \ + --wait \ + --debug \ + --create-namespace \ + --namespace=${simulator[namespace]} \ + --set imageName=${simulator[imageName]} \ + --set imagePullPolicy="Always" \ + --set name=${simulator[name]} \ + --set replicas=${simulator[replicas]} \ + --set port=${simulator[port]} \ + --set httpserver.requestInterval=${simulator[httpInterval]} \ + --set httpserver.endpoint="${httpserver[name]}.${httpserver[namespace]}.svc.cluster.local" \ + --set httpserver.port="${httpserver[port]}" \ + --set kafka.address="${kafka[name]}.${kafka[namespace]}.svc.cluster.local:9092" \ + --set kafka.topic=${kafka[topic]} \ + --set kafka.requestInterval=${simulator[kafkaInterval]} \ + --set otel.exporter="otlp" \ + --set otlp.endpoint="${otelcollectors[endpoint]}" \ + "./infra/helm/${application}/chart"