Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .chloggen/middleware-http.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: confighttp

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add HTTP middleware support.

# One or more tracking issues or pull requests related to the change
issues: [12603, 9591, 7441]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
3 changes: 3 additions & 0 deletions cmd/builder/internal/builder/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var replaceModules = []string{
"/config/configcompression",
"/config/configgrpc",
"/config/confighttp",
"/config/configmiddleware",
"/config/confignet",
"/config/configopaque",
"/config/configretry",
Expand Down Expand Up @@ -79,6 +80,8 @@ var replaceModules = []string{
"/extension/extensionauth",
"/extension/extensionauth/extensionauthtest",
"/extension/extensioncapabilities",
"/extension/extensionmiddleware",
"/extension/extensionmiddleware/extensionmiddlewaretest",
"/extension/extensiontest",
"/extension/zpagesextension",
"/extension/xextension",
Expand Down
3 changes: 3 additions & 0 deletions cmd/otelcorecol/builder-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ replaces:
- go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
- go.opentelemetry.io/collector/config/configgrpc => ../../config/configgrpc
- go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
- go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
- go.opentelemetry.io/collector/config/confignet => ../../config/confignet
- go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
- go.opentelemetry.io/collector/config/configretry => ../../config/configretry
Expand Down Expand Up @@ -79,6 +80,8 @@ replaces:
- go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
- go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
- go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
- go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
- go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
- go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
- go.opentelemetry.io/collector/extension/memorylimiterextension => ../../extension/memorylimiterextension
- go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
Expand Down
8 changes: 8 additions & 0 deletions cmd/otelcorecol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ require (
go.opentelemetry.io/collector/config/configcompression v1.30.0 // indirect
go.opentelemetry.io/collector/config/configgrpc v0.124.0 // indirect
go.opentelemetry.io/collector/config/confighttp v0.124.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v0.0.0-00010101000000-000000000000 // indirect
go.opentelemetry.io/collector/config/confignet v1.30.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.30.0 // indirect
go.opentelemetry.io/collector/config/configretry v1.30.0 // indirect
Expand All @@ -107,6 +108,7 @@ require (
go.opentelemetry.io/collector/exporter/xexporter v0.124.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.30.0 // indirect
go.opentelemetry.io/collector/extension/extensioncapabilities v0.124.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect
go.opentelemetry.io/collector/extension/extensiontest v0.124.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.124.0 // indirect
go.opentelemetry.io/collector/featuregate v1.30.0 // indirect
Expand Down Expand Up @@ -186,6 +188,8 @@ replace go.opentelemetry.io/collector/config/configgrpc => ../../config/configgr

replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp

replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware

replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet

replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
Expand Down Expand Up @@ -252,6 +256,10 @@ replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest

replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities

replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware

replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest

replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest

replace go.opentelemetry.io/collector/extension/memorylimiterextension => ../../extension/memorylimiterextension
Expand Down
2 changes: 2 additions & 0 deletions config/confighttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ README](../configtls/README.md).
- [`http2_ping_timeout`](https://pkg.go.dev/golang.org/x/net/http2#Transport)
- [`cookies`](https://pkg.go.dev/net/http#CookieJar)
- [`enabled`] if enabled, the client will store cookies from server responses and reuse them in subsequent requests.
- [`middlewares`](../configmiddleware/README.md)

Example:

Expand Down Expand Up @@ -107,6 +108,7 @@ will not be enabled.
- [`tls`](../configtls/README.md)
- [`auth`](../configauth/README.md)
- `request_params`: a list of query parameter names to add to the auth context, along with the HTTP headers
- [`middlewares`](../configmiddleware/README.md)

You can enable [`attribute processor`][attribute-processor] to append any http header to span's attribute using custom key. You also need to enable the "include_metadata"

Expand Down
255 changes: 255 additions & 0 deletions config/confighttp/client_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package confighttp

import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configmiddleware"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
)

// testClientMiddleware is a test middleware that appends a string to the response body
type testClientMiddleware struct {
extension.Extension
extensionmiddleware.GetHTTPRoundTripperFunc
}

func newTestClientMiddleware(name string) component.Component {
return &testClientMiddleware{
Extension: extensionmiddlewaretest.NewNop(),
GetHTTPRoundTripperFunc: func(transport http.RoundTripper) (http.RoundTripper, error) {
return extensionmiddlewaretest.HTTPClientFunc(
func(req *http.Request) (*http.Response, error) {
resp, err := transport.RoundTrip(req)
if err != nil {
return resp, err
}

// Read the original body
body, err := io.ReadAll(resp.Body)
if err != nil {
return resp, err
}
_ = resp.Body.Close()

// Create a new body with the appended text
newBody := string(body) + "\r\noutput by " + name

// Replace the response body
resp.Body = io.NopCloser(strings.NewReader(newBody))
resp.ContentLength = int64(len(newBody))

return resp, nil
}), nil
},
}
}

func newTestClientConfig(name string) configmiddleware.Config {
return configmiddleware.Config{
ID: component.MustNewID(name),
}
}

func TestClientMiddlewares(t *testing.T) {
// Create a test server that returns "OK"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}))
defer server.Close()

// Register two test extensions
host := &mockHost{
ext: map[component.ID]component.Component{
component.MustNewID("test1"): newTestClientMiddleware("test1"),
component.MustNewID("test2"): newTestClientMiddleware("test2"),
},
}

// Test with different middleware configurations
testCases := []struct {
name string
middlewares []configmiddleware.Config
expectedOutput string
}{
{
name: "no_middlewares",
middlewares: nil,
expectedOutput: "OK",
},
{
name: "single_middleware",
middlewares: []configmiddleware.Config{
newTestClientConfig("test1"),
},
expectedOutput: "OK\r\noutput by test1",
},
{
name: "multiple_middlewares",
middlewares: []configmiddleware.Config{
newTestClientConfig("test1"),
newTestClientConfig("test2"),
},
expectedOutput: "OK\r\noutput by test2\r\noutput by test1",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create HTTP client config with the test middlewares
clientConfig := ClientConfig{
Endpoint: server.URL,
Middlewares: tc.middlewares,
}

// Create the client
client, err := clientConfig.ToClient(context.Background(), host, componenttest.NewNopTelemetrySettings())
require.NoError(t, err)

// Create a request to the test server
req, err := http.NewRequest(http.MethodGet, server.URL, nil)
require.NoError(t, err)

// Send the request
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

// Check the response
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, tc.expectedOutput, string(body))
})
}
}

func TestClientMiddlewareErrors(t *testing.T) {
// Create a test server that returns "OK"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}))
defer server.Close()

// Test cases for HTTP client middleware errors
httpTests := []struct {
name string
host component.Host
config ClientConfig
errText string
}{
{
name: "extension_not_found",
host: &mockHost{
ext: map[component.ID]component.Component{},
},
config: ClientConfig{
Endpoint: server.URL,
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "get_round_tripper_fails",
host: &mockHost{
ext: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("http middleware error")),
},
},
config: ClientConfig{
Endpoint: server.URL,
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "http middleware error",
},
}

for _, tc := range httpTests {
t.Run(tc.name, func(t *testing.T) {
// Trying to create the client should fail
_, err := tc.config.ToClient(context.Background(), tc.host, componenttest.NewNopTelemetrySettings())
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}

// Test failures for gRPC client middlewares by creating a mock implementation
// that can fail in similar ways to HTTP clients
func TestGRPCClientMiddlewareErrors(t *testing.T) {
// Test cases for gRPC client middleware errors
grpcTests := []struct {
name string
host component.Host
config ClientConfig
errText string
}{
{
name: "grpc_extension_not_found",
host: &mockHost{
ext: map[component.ID]component.Component{},
},
config: ClientConfig{
Endpoint: "localhost:1234",
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "grpc_get_client_options_fails",
host: &mockHost{
ext: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("grpc middleware error")),
},
},
config: ClientConfig{
Endpoint: "localhost:1234",
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "grpc middleware error",
},
}

for _, tc := range grpcTests {
t.Run(tc.name, func(t *testing.T) {
// For gRPC, we need to use the configgrpc.ClientConfig structure
// We'll test the middleware failure path here using the HTTP client approach,
// as the middleware resolution logic is the same
_, err := tc.config.ToClient(context.Background(), tc.host, componenttest.NewNopTelemetrySettings())
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}
Loading
Loading