Skip to content

Commit 56be411

Browse files
authored
dcs: Update claimer interface to accept all the gateway ids (#7916)
1 parent e301f23 commit 56be411

7 files changed

Lines changed: 133 additions & 41 deletions

File tree

pkg/deviceclaimingserver/gateways/gateways.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ func ParseGatewayEUIRanges(conf map[string][]string) (map[string][]dcstypes.EUI6
9292
type Claimer interface {
9393
// Claim claims a gateway.
9494
Claim(
95-
ctx context.Context, eui types.EUI64, ownerToken string, clusterAddress string,
95+
ctx context.Context, ids *ttnpb.GatewayIdentifiers, ownerToken string, clusterAddress string,
9696
) (*dcstypes.GatewayMetadata, error)
9797
// Unclaim unclaims a gateway.
98-
Unclaim(ctx context.Context, eui types.EUI64) error
98+
Unclaim(ctx context.Context, ids *ttnpb.GatewayIdentifiers) error
9999
// IsManagedGateway returns true if the gateway is a managed gateway.
100100
IsManagedGateway(ctx context.Context, eui types.EUI64) (bool, error)
101101
}

pkg/deviceclaimingserver/gateways/ttgc/lbscups.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,10 @@ var (
3838
)
3939

4040
func (u *Upstream) claimLBSCUPSGateway(
41-
ctx context.Context, eui types.EUI64, ownerToken, clusterAddress string,
41+
ctx context.Context, ids *ttnpb.GatewayIdentifiers, ownerToken, clusterAddress string,
4242
) (*dcstypes.GatewayMetadata, error) {
4343
logger := log.FromContext(ctx)
44-
45-
ids := &ttnpb.GatewayIdentifiers{
46-
Eui: eui.Bytes(),
47-
}
44+
eui := types.MustEUI64(ids.Eui).OrZero()
4845

4946
// Create CUPS and LNS API keys for the gateway. The CUPS key will be used as gateway token when claiming on TTGC and
5047
// the LNS key will be returned in the metadata. The caller is responsible for updating the LNS key in the gateway.
@@ -165,7 +162,7 @@ func (u *Upstream) createAPIKeys(
165162

166163
cupsKey, err = gatewayAccess.CreateAPIKey(ctx, &ttnpb.CreateGatewayAPIKeyRequest{
167164
GatewayIds: ids,
168-
Name: fmt.Sprintf("LBS CUPS Key (TTGC claim), generated %s", time.Now().UTC().Format(time.RFC3339)),
165+
Name: fmt.Sprintf("LBS CUPS Key (TTGC), %s", time.Now().UTC().Format(time.RFC3339)),
169166
Rights: []ttnpb.Right{
170167
ttnpb.Right_RIGHT_GATEWAY_INFO,
171168
ttnpb.Right_RIGHT_GATEWAY_SETTINGS_BASIC,
@@ -179,7 +176,7 @@ func (u *Upstream) createAPIKeys(
179176

180177
lnsKey, err = gatewayAccess.CreateAPIKey(ctx, &ttnpb.CreateGatewayAPIKeyRequest{
181178
GatewayIds: ids,
182-
Name: fmt.Sprintf("LBS LNS Key (TTGC claim), generated %s", time.Now().UTC().Format(time.RFC3339)),
179+
Name: fmt.Sprintf("LBS LNS Key (TTGC), %s", time.Now().UTC().Format(time.RFC3339)),
183180
Rights: []ttnpb.Right{
184181
ttnpb.Right_RIGHT_GATEWAY_LINK,
185182
},

pkg/deviceclaimingserver/gateways/ttgc/ttgc.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ func New(ctx context.Context, c component, config ttgc.Config) (*Upstream, error
7070
type claimOption struct {
7171
protocol northboundv1.GatewayProtocolIdentifier
7272
authMethod northboundv1.AuthenticationMethod
73-
handler func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error)
73+
handler func(context.Context, *ttnpb.GatewayIdentifiers, string, string) (*dcstypes.GatewayMetadata, error)
7474
}
7575

7676
// Claim implements gateways.GatewayClaimer.
7777
func (u *Upstream) Claim(
78-
ctx context.Context, eui types.EUI64, ownerToken, clusterAddress string,
78+
ctx context.Context, ids *ttnpb.GatewayIdentifiers, ownerToken, clusterAddress string,
7979
) (*dcstypes.GatewayMetadata, error) {
80+
eui := types.MustEUI64(ids.Eui).OrZero()
81+
8082
// Get the gateway description to verify what protocol it supports.
8183
gtwClient := northboundv1.NewGatewayServiceClient(u.client)
8284
desc, err := gtwClient.Describe(ctx, &northboundv1.GatewayServiceDescribeRequest{
@@ -103,7 +105,7 @@ func (u *Upstream) Claim(
103105
// Select the first supported claiming option and use its handler.
104106
for _, option := range claimPreferences {
105107
if u.supportsOption(desc, option) {
106-
return option.handler(ctx, eui, ownerToken, clusterAddress)
108+
return option.handler(ctx, ids, ownerToken, clusterAddress)
107109
}
108110
}
109111

@@ -127,9 +129,11 @@ func (*Upstream) supportsOption(
127129
}
128130

129131
// Unclaim implements gateways.GatewayClaimer.
130-
func (u *Upstream) Unclaim(ctx context.Context, eui types.EUI64) error {
132+
func (u *Upstream) Unclaim(ctx context.Context, ids *ttnpb.GatewayIdentifiers) error {
133+
eui := types.MustEUI64(ids.Eui).OrZero()
134+
131135
// Delete the CUPS and LNS API keys for the gateway.
132-
if err := u.deleteAPIKeys(ctx, &ttnpb.GatewayIdentifiers{Eui: eui.Bytes()}); err != nil {
136+
if err := u.deleteAPIKeys(ctx, ids); err != nil {
133137
// Don't fail unclaiming if deleting the API keys fails.
134138
log.FromContext(ctx).WithError(err).Warn("Failed to delete API keys for gateway")
135139
}

pkg/deviceclaimingserver/gateways/ttgc/ttiv1.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ import (
3636
// 3. Upsert a Geolocation profile
3737
// 4. Update the gateway with the profiles
3838
func (u *Upstream) claimTTIV1Gateway(
39-
ctx context.Context, eui types.EUI64, ownerToken, clusterAddress string,
39+
ctx context.Context, ids *ttnpb.GatewayIdentifiers, ownerToken, clusterAddress string,
4040
) (*dcstypes.GatewayMetadata, error) {
4141
logger := log.FromContext(ctx)
42+
eui := types.MustEUI64(ids.Eui).OrZero()
4243

4344
// Claim the gateway.
4445
gtwClient := northboundv1.NewGatewayServiceClient(u.client)

pkg/deviceclaimingserver/grpc_gateways.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package deviceclaimingserver
1717
import (
1818
"context"
1919
"fmt"
20+
"strings"
2021

2122
"go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways"
2223
"go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/observability"
@@ -95,9 +96,13 @@ func (gcls *gatewayClaimingServer) Claim(
9596
logger = logger.WithFields(log.Fields(
9697
"gateway_eui", gatewayEUI,
9798
))
99+
gatewayID := req.TargetGatewayId
100+
if gatewayID == "" {
101+
gatewayID = strings.ToLower(gatewayEUI.String())
102+
}
98103
ids = &ttnpb.GatewayIdentifiers{
99104
Eui: gatewayEUI.Bytes(),
100-
GatewayId: req.TargetGatewayId,
105+
GatewayId: gatewayID,
101106
}
102107

103108
// Check if the gateway already exists.
@@ -113,21 +118,24 @@ func (gcls *gatewayClaimingServer) Claim(
113118
Ids: ids,
114119
}
115120

116-
_, err = gcls.registry.Create(ctx, &ttnpb.CreateGatewayRequest{
121+
created, err := gcls.registry.Create(ctx, &ttnpb.CreateGatewayRequest{
117122
Gateway: gateway,
118123
Collaborator: req.GetCollaborator(),
119124
})
120125
if err != nil {
121126
return nil, errCreateGateway.WithCause(err)
122127
}
123-
defer func() {
128+
if createdIDs := created.GetIds(); createdIDs != nil {
129+
ids = createdIDs
130+
}
131+
defer func(ids *ttnpb.GatewayIdentifiers) {
124132
if retErr != nil {
125133
logger.Warn("Failed to claim gateway, deleting created gateway")
126134
if _, delErr := gcls.registry.Delete(ctx, ids); delErr != nil {
127135
logger.WithError(delErr).Warn("Failed to delete created gateway after failed claim")
128136
}
129137
}
130-
}()
138+
}(ids)
131139

132140
// Support clients that only set a single frequency plan.
133141
if len(req.TargetFrequencyPlanIds) == 0 && req.TargetFrequencyPlanId != "" { // nolint:staticcheck
@@ -141,7 +149,7 @@ func (gcls *gatewayClaimingServer) Claim(
141149
}
142150

143151
// Claim the gateway on the upstream.
144-
res, err := claimer.Claim(ctx, gatewayEUI, string(authCode), req.TargetGatewayServerAddress)
152+
res, err := claimer.Claim(ctx, ids, string(authCode), req.TargetGatewayServerAddress)
145153
if err != nil {
146154
observability.RegisterFailClaim(ctx, ids.GetEntityIdentifiers(), err)
147155
return nil, errClaim.WithCause(err)
@@ -151,7 +159,7 @@ func (gcls *gatewayClaimingServer) Claim(
151159
defer func(ids *ttnpb.GatewayIdentifiers) {
152160
if retErr != nil {
153161
observability.RegisterAbortClaim(ctx, ids.GetEntityIdentifiers(), retErr)
154-
if err := claimer.Unclaim(ctx, gatewayEUI); err != nil {
162+
if err := claimer.Unclaim(ctx, ids); err != nil {
155163
logger.WithError(err).Warn("Failed to unclaim gateway")
156164
}
157165
return
@@ -256,7 +264,7 @@ func (gcls gatewayClaimingServer) Unclaim(ctx context.Context, req *ttnpb.Gatewa
256264
return nil, errGatewayClaimingNotSupported.WithAttributes("eui", gatewayEUI)
257265
}
258266

259-
if err := claimer.Unclaim(ctx, gatewayEUI); err != nil {
267+
if err := claimer.Unclaim(ctx, gtw.Ids); err != nil {
260268
observability.RegisterFailUnclaim(ctx, gtw.GetEntityIdentifiers(), err)
261269
return nil, err
262270
}

pkg/deviceclaimingserver/grpc_gateways_test.go

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,10 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
181181
Name string
182182
Req *ttnpb.ClaimGatewayRequest
183183
CallOpt grpc.CallOption
184-
ClaimFunc func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error)
184+
ClaimFunc func(context.Context, *ttnpb.GatewayIdentifiers, string, string) (*dcstypes.GatewayMetadata, error)
185185
CreateFunc func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error)
186186
UpdateFunc func(context.Context, *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error)
187-
UnclaimFunc func(context.Context, types.EUI64) error
187+
UnclaimFunc func(context.Context, *ttnpb.GatewayIdentifiers) error
188188
DeleteFunc func(context.Context, *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error)
189189
ErrorAssertion func(error) bool
190190
}{
@@ -302,7 +302,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
302302
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
303303
return in.Gateway, nil
304304
},
305-
ClaimFunc: func(_ context.Context, _ types.EUI64, _, _ string) (*dcstypes.GatewayMetadata, error) {
305+
ClaimFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers, _, _ string) (*dcstypes.GatewayMetadata, error) {
306306
return nil, errClaim.New()
307307
},
308308
UpdateFunc: func(_ context.Context, in *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error) {
@@ -327,7 +327,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
327327
TargetGatewayServerAddress: "things.example.com",
328328
},
329329
CallOpt: authorizedCallOpt,
330-
ClaimFunc: func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) {
330+
ClaimFunc: func(context.Context, *ttnpb.GatewayIdentifiers, string, string) (*dcstypes.GatewayMetadata, error) {
331331
return &dcstypes.GatewayMetadata{}, nil
332332
},
333333
CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
@@ -339,8 +339,8 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
339339
DeleteFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
340340
return &emptypb.Empty{}, nil
341341
},
342-
UnclaimFunc: func(_ context.Context, eui types.EUI64) error {
343-
if eui.Equal(supportedEUI) {
342+
UnclaimFunc: func(_ context.Context, ids *ttnpb.GatewayIdentifiers) error {
343+
if types.MustEUI64(ids.Eui).OrZero().Equal(supportedEUI) {
344344
return nil
345345
}
346346
return errUnclaim.New()
@@ -361,7 +361,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
361361
TargetGatewayServerAddress: "things.example.com",
362362
},
363363
CallOpt: authorizedCallOpt,
364-
ClaimFunc: func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) {
364+
ClaimFunc: func(context.Context, *ttnpb.GatewayIdentifiers, string, string) (*dcstypes.GatewayMetadata, error) {
365365
return &dcstypes.GatewayMetadata{}, nil
366366
},
367367
CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
@@ -373,7 +373,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
373373
DeleteFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
374374
return &emptypb.Empty{}, nil
375375
},
376-
UnclaimFunc: func(context.Context, types.EUI64) error {
376+
UnclaimFunc: func(context.Context, *ttnpb.GatewayIdentifiers) error {
377377
return errUnclaim.New()
378378
},
379379
ErrorAssertion: errors.IsAborted,
@@ -391,7 +391,66 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
391391
TargetGatewayId: "test-gateway",
392392
TargetGatewayServerAddress: "things.example.com",
393393
},
394-
ClaimFunc: func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) {
394+
ClaimFunc: func(context.Context, *ttnpb.GatewayIdentifiers, string, string) (*dcstypes.GatewayMetadata, error) {
395+
return &dcstypes.GatewayMetadata{}, nil
396+
},
397+
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
398+
return in.Gateway, nil
399+
},
400+
UpdateFunc: func(_ context.Context, in *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error) {
401+
return in.Gateway, nil
402+
},
403+
CallOpt: authorizedCallOpt,
404+
},
405+
{
406+
Name: "Claim/EmptyTargetGatewayIDDefaultsToEUIAndDeletesOnFailedClaim",
407+
Req: &ttnpb.ClaimGatewayRequest{
408+
Collaborator: userID.GetOrganizationOrUserIdentifiers(),
409+
SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{
410+
AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{
411+
GatewayEui: supportedEUI.Bytes(),
412+
AuthenticationCode: claimAuthCode,
413+
},
414+
},
415+
TargetGatewayServerAddress: "things.example.com",
416+
},
417+
CallOpt: authorizedCallOpt,
418+
ClaimFunc: func(
419+
_ context.Context, ids *ttnpb.GatewayIdentifiers, _, _ string,
420+
) (*dcstypes.GatewayMetadata, error) {
421+
a.So(ids.GatewayId, should.Equal, "58a0cbfffe800001")
422+
a.So(ids.Eui, should.Resemble, supportedEUI.Bytes())
423+
return nil, errClaim.New()
424+
},
425+
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
426+
a.So(in.Gateway.GetIds().GetGatewayId(), should.Equal, "58a0cbfffe800001")
427+
return in.Gateway, nil
428+
},
429+
DeleteFunc: func(_ context.Context, ids *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
430+
a.So(ids.GatewayId, should.Equal, "58a0cbfffe800001")
431+
a.So(ids.Eui, should.Resemble, supportedEUI.Bytes())
432+
return &emptypb.Empty{}, nil
433+
},
434+
ErrorAssertion: errors.IsAborted,
435+
},
436+
{
437+
Name: "Claim/ForwardsGatewayIdentifiers",
438+
Req: &ttnpb.ClaimGatewayRequest{
439+
Collaborator: userID.GetOrganizationOrUserIdentifiers(),
440+
SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{
441+
AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{
442+
GatewayEui: supportedEUI.Bytes(),
443+
AuthenticationCode: claimAuthCode,
444+
},
445+
},
446+
TargetGatewayId: "forwarded-gateway",
447+
TargetGatewayServerAddress: "things.example.com",
448+
},
449+
ClaimFunc: func(
450+
_ context.Context, ids *ttnpb.GatewayIdentifiers, _, _ string,
451+
) (*dcstypes.GatewayMetadata, error) {
452+
a.So(ids.GatewayId, should.Equal, "forwarded-gateway")
453+
a.So(ids.Eui, should.Resemble, supportedEUI.Bytes())
395454
return &dcstypes.GatewayMetadata{}, nil
396455
},
397456
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
@@ -437,7 +496,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
437496
Req *ttnpb.GatewayIdentifiers
438497
CallOpt grpc.CallOption
439498
GetFunc func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error)
440-
UnclaimFunc func(context.Context, types.EUI64) error
499+
UnclaimFunc func(context.Context, *ttnpb.GatewayIdentifiers) error
441500
ErrorAssertion func(error) bool
442501
}{
443502
{
@@ -508,7 +567,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
508567
GatewayServerAddress: "test.example.com",
509568
}, nil
510569
},
511-
UnclaimFunc: func(context.Context, types.EUI64) error {
570+
UnclaimFunc: func(context.Context, *ttnpb.GatewayIdentifiers) error {
512571
return errUnclaim.New()
513572
},
514573
CallOpt: authorizedCallOpt,
@@ -528,7 +587,28 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
528587
GatewayServerAddress: "test.example.com",
529588
}, nil
530589
},
531-
UnclaimFunc: func(context.Context, types.EUI64) error {
590+
UnclaimFunc: func(context.Context, *ttnpb.GatewayIdentifiers) error {
591+
return nil
592+
},
593+
CallOpt: authorizedCallOpt,
594+
},
595+
{
596+
Name: "Unclaim/ForwardsGatewayIdentifiers",
597+
Req: &ttnpb.GatewayIdentifiers{
598+
GatewayId: "forwarded-gateway",
599+
},
600+
GetFunc: func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) {
601+
return &ttnpb.Gateway{
602+
Ids: &ttnpb.GatewayIdentifiers{
603+
GatewayId: "forwarded-gateway",
604+
Eui: supportedEUI.Bytes(),
605+
},
606+
GatewayServerAddress: "test.example.com",
607+
}, nil
608+
},
609+
UnclaimFunc: func(_ context.Context, ids *ttnpb.GatewayIdentifiers) error {
610+
a.So(ids.GatewayId, should.Equal, "forwarded-gateway")
611+
a.So(ids.Eui, should.Resemble, supportedEUI.Bytes())
532612
return nil
533613
},
534614
CallOpt: authorizedCallOpt,

pkg/deviceclaimingserver/util_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
"google.golang.org/protobuf/types/known/emptypb"
2626
)
2727

28-
// MockEndDeviceClaimer is a mock Claimer.
28+
// MockEndDeviceClaimer is a mock end device Claimer.
2929
type MockEndDeviceClaimer struct {
3030
JoinEUIs []types.EUI64
3131

@@ -81,24 +81,26 @@ func (m MockEndDeviceClaimer) BatchUnclaim(
8181
type MockGatewayClaimer struct {
8282
EUIs []types.EUI64
8383

84-
ClaimFunc func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error)
85-
UnclaimFunc func(context.Context, types.EUI64) error
84+
ClaimFunc func(
85+
context.Context, *ttnpb.GatewayIdentifiers, string, string,
86+
) (*dcstypes.GatewayMetadata, error)
87+
UnclaimFunc func(context.Context, *ttnpb.GatewayIdentifiers) error
8688
IsManagedGatewayFunc func(context.Context, types.EUI64) (bool, error)
8789
}
8890

8991
// Claim implements gateways.Claimer.
9092
func (claimer MockGatewayClaimer) Claim(
9193
ctx context.Context,
92-
eui types.EUI64,
94+
ids *ttnpb.GatewayIdentifiers,
9395
ownerToken string,
9496
clusterAddress string,
9597
) (*dcstypes.GatewayMetadata, error) {
96-
return claimer.ClaimFunc(ctx, eui, ownerToken, clusterAddress)
98+
return claimer.ClaimFunc(ctx, ids, ownerToken, clusterAddress)
9799
}
98100

99101
// Unclaim implements gateways.Claimer.
100-
func (claimer MockGatewayClaimer) Unclaim(ctx context.Context, eui types.EUI64) error {
101-
return claimer.UnclaimFunc(ctx, eui)
102+
func (claimer MockGatewayClaimer) Unclaim(ctx context.Context, ids *ttnpb.GatewayIdentifiers) error {
103+
return claimer.UnclaimFunc(ctx, ids)
102104
}
103105

104106
// IsManagedGateway implements gateways.Claimer.

0 commit comments

Comments
 (0)