Skip to content

Commit abaa070

Browse files
Add OpAMP-Instance-UID request header
This implements the new "OpAMP-Instance-UID" request header defined by the spec: open-telemetry/opamp-spec#270
1 parent 433dc5c commit abaa070

File tree

5 files changed

+107
-6
lines changed

5 files changed

+107
-6
lines changed

client/clientimpl_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ func genNewInstanceUid(t *testing.T) types.InstanceUid {
105105

106106
func prepareSettings(t *testing.T, settings *types.StartSettings, c OpAMPClient) {
107107
// Autogenerate instance id.
108-
settings.InstanceUid = genNewInstanceUid(t)
108+
if settings.InstanceUid == [16]byte{} {
109+
settings.InstanceUid = genNewInstanceUid(t)
110+
}
109111

110112
// Make sure correct URL scheme is used, based on the type of the OpAMP client.
111113
u, err := url.Parse(settings.OpAMPServerURL)

client/httpclient_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313
"time"
1414

15+
"github.com/google/uuid"
1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/mock"
1718
"github.com/stretchr/testify/require"
@@ -433,3 +434,62 @@ func TestHTTPReportsAvailableComponents(t *testing.T) {
433434
})
434435
}
435436
}
437+
438+
func TestHTTPSenderOpAMPInstanceUIDHeader(t *testing.T) {
439+
client := NewHTTP(nil)
440+
441+
var instanceUids [2][16]byte
442+
443+
// We run twice, once with initial instance UID 1, then with instance UID 2 that server
444+
// asks the client to accept, to verify both scenarios.
445+
for i := 0; i < 2; i++ {
446+
instanceUids[i] = [16]byte{byte(i + 1)}
447+
}
448+
449+
// Start a server.
450+
srv := internal.StartMockServer(t)
451+
var connCnt atomic.Int32
452+
srv.OnConnect = func(r *http.Request) {
453+
// Get request header
454+
hdrUid := r.Header.Get("OpAMP-Instance-UID")
455+
456+
// Get expected UID
457+
uidIdx := connCnt.Load()
458+
if uidIdx > 1 {
459+
uidIdx = 1
460+
}
461+
uid, err := uuid.FromBytes(instanceUids[uidIdx][:])
462+
require.NoError(t, err)
463+
464+
// Make sure we got expected header
465+
assert.EqualValues(t, uid.String(), hdrUid)
466+
467+
connCnt.Add(1)
468+
}
469+
srv.OnMessage = func(msg *protobufs.AgentToServer) *protobufs.ServerToAgent {
470+
return &protobufs.ServerToAgent{
471+
InstanceUid: msg.InstanceUid,
472+
AgentIdentification: &protobufs.AgentIdentification{
473+
NewInstanceUid: instanceUids[1][:], // Ask client to change its Instance UID
474+
},
475+
Flags: uint64(protobufs.ServerToAgentFlags_ServerToAgentFlags_ReportFullState),
476+
}
477+
}
478+
479+
header := http.Header{}
480+
481+
// Start a client.
482+
settings := types.StartSettings{
483+
OpAMPServerURL: "ws://" + srv.Endpoint,
484+
Header: header,
485+
InstanceUid: instanceUids[0],
486+
}
487+
startClient(t, settings, client)
488+
489+
// Wait for 2 connection attempts.
490+
eventually(t, func() bool { return connCnt.Load() >= 2 })
491+
492+
// Shutdown the Server and the client.
493+
srv.Close()
494+
_ = client.Stop(context.Background())
495+
}

client/internal/httpsender.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616

1717
"github.com/cenkalti/backoff/v4"
18+
"github.com/google/uuid"
1819
"google.golang.org/protobuf/proto"
1920

2021
"github.com/open-telemetry/opamp-go/client/internal/utils"
@@ -348,11 +349,17 @@ func (h *HTTPSender) prepareRequest(ctx context.Context) (*requestWrapper, error
348349
} else {
349350
req.bodyReader = bodyReader(data)
350351
}
351-
if err != nil {
352-
return nil, err
353-
}
354352

355353
req.Header = h.getHeader()
354+
355+
if msgToSend.InstanceUid != nil {
356+
uid, err := uuid.FromBytes(msgToSend.InstanceUid)
357+
if err != nil {
358+
return nil, err
359+
}
360+
req.Header.Add(headerOpAMPInstanceUID, uid.String())
361+
}
362+
356363
return &req, nil
357364
}
358365

client/internal/httpsender_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import (
1313
"testing"
1414
"time"
1515

16+
"github.com/google/uuid"
1617
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
1719

1820
"github.com/open-telemetry/opamp-go/client/types"
1921
sharedinternal "github.com/open-telemetry/opamp-go/internal"
@@ -708,3 +710,32 @@ func setupTestSender(_ *testing.T, url string) *HTTPSender {
708710
sender.url = url
709711
return sender
710712
}
713+
714+
func TestHTTPSenderOpAMPInstanceUIDHeader(t *testing.T) {
715+
uidBytes := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
716+
uid, err := uuid.FromBytes(uidBytes)
717+
require.NoError(t, err)
718+
719+
srv := StartMockServer(t)
720+
srv.OnRequest = func(w http.ResponseWriter, r *http.Request) {
721+
assert.EqualValues(t, r.Header.Get(headerOpAMPInstanceUID), uid.String())
722+
w.WriteHeader(http.StatusOK)
723+
}
724+
ctx, cancel := context.WithCancel(context.Background())
725+
url := "http://" + srv.Endpoint
726+
sender := NewHTTPSender(&sharedinternal.NopLogger{})
727+
728+
sender.NextMessage().Update(func(msg *protobufs.AgentToServer) {
729+
msg.InstanceUid = uidBytes
730+
})
731+
sender.callbacks = types.Callbacks{
732+
OnConnect: func(ctx context.Context) {},
733+
}
734+
735+
sender.url = url
736+
resp, err := sender.sendRequestWithRetries(ctx)
737+
assert.NoError(t, err)
738+
assert.Equal(t, http.StatusOK, resp.StatusCode)
739+
cancel()
740+
srv.Close()
741+
}

client/internal/mockserver.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ type MockServer struct {
3535
}
3636

3737
const (
38-
headerContentType = "Content-Type"
39-
contentTypeProtobuf = "application/x-protobuf"
38+
headerContentType = "Content-Type"
39+
contentTypeProtobuf = "application/x-protobuf"
40+
headerOpAMPInstanceUID = "OpAMP-Instance-UID"
4041
)
4142

4243
func newMockServer(t *testing.T) (*MockServer, *http.ServeMux) {

0 commit comments

Comments
 (0)