Skip to content

Commit a3670ab

Browse files
committed
dcs: Create gateway at the start of the Claim to attach keys to gateway
1 parent 34bf3a5 commit a3670ab

4 files changed

Lines changed: 111 additions & 13 deletions

File tree

pkg/deviceclaimingserver/grpc_gateways.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
2828
"go.thethings.network/lorawan-stack/v3/pkg/types"
2929
"google.golang.org/protobuf/types/known/emptypb"
30+
"google.golang.org/protobuf/types/known/fieldmaskpb"
3031
)
3132

3233
type peerAccess interface {
@@ -107,6 +108,27 @@ func (gcls *gatewayClaimingServer) Claim(
107108
return nil, err
108109
}
109110

111+
// Create the gateway in the IS.
112+
gateway := &ttnpb.Gateway{
113+
Ids: ids,
114+
}
115+
116+
_, err = gcls.registry.Create(ctx, &ttnpb.CreateGatewayRequest{
117+
Gateway: gateway,
118+
Collaborator: req.GetCollaborator(),
119+
})
120+
if err != nil {
121+
return nil, errCreateGateway.WithCause(err)
122+
}
123+
defer func() {
124+
if retErr != nil {
125+
logger.Warn("Failed to claim gateway, deleting created gateway")
126+
if _, delErr := gcls.registry.Delete(ctx, ids); delErr != nil {
127+
logger.WithError(delErr).Warn("Failed to delete created gateway after failed claim")
128+
}
129+
}
130+
}()
131+
110132
// Support clients that only set a single frequency plan.
111133
if len(req.TargetFrequencyPlanIds) == 0 && req.TargetFrequencyPlanId != "" { // nolint:staticcheck
112134
req.TargetFrequencyPlanIds = []string{req.TargetFrequencyPlanId} // nolint:staticcheck
@@ -125,7 +147,7 @@ func (gcls *gatewayClaimingServer) Claim(
125147
return nil, errClaim.WithCause(err)
126148
}
127149

128-
// Unclaim if creation fails.
150+
// Unclaim if update fails.
129151
defer func(ids *ttnpb.GatewayIdentifiers) {
130152
if retErr != nil {
131153
observability.RegisterAbortClaim(ctx, ids.GetEntityIdentifiers(), retErr)
@@ -137,8 +159,9 @@ func (gcls *gatewayClaimingServer) Claim(
137159
observability.RegisterSuccessClaim(ctx, ids.GetEntityIdentifiers())
138160
}(ids)
139161

140-
// Create the gateway in the IS.
141-
gateway := &ttnpb.Gateway{
162+
// Update the gateway in the IS. If the update fails, the gateway will be unclaimed in the above deferred function
163+
// and deleted in the previous one.
164+
gateway = &ttnpb.Gateway{
142165
Ids: ids,
143166
GatewayServerAddress: req.TargetGatewayServerAddress,
144167
EnforceDutyCycle: true,
@@ -147,9 +170,24 @@ func (gcls *gatewayClaimingServer) Claim(
147170
Antennas: res.Antennas,
148171
}
149172

150-
_, err = gcls.registry.Create(ctx, &ttnpb.CreateGatewayRequest{
151-
Gateway: gateway,
152-
Collaborator: req.GetCollaborator(),
173+
fieldMask := &fieldmaskpb.FieldMask{
174+
Paths: []string{
175+
"gateway_server_address",
176+
"enforce_duty_cycle",
177+
"require_authenticated_connection",
178+
"frequency_plan_ids",
179+
"antennas",
180+
},
181+
}
182+
183+
if res.LBSLNSKey != nil {
184+
gateway.LbsLnsSecret = &ttnpb.Secret{Value: []byte(res.LBSLNSKey.Key)}
185+
fieldMask.Paths = append(fieldMask.Paths, "lbs_lns_secret")
186+
}
187+
188+
_, err = gcls.registry.Update(ctx, &ttnpb.UpdateGatewayRequest{
189+
Gateway: gateway,
190+
FieldMask: fieldMask,
153191
})
154192
if err != nil {
155193
return nil, errCreateGateway.WithCause(err)

pkg/deviceclaimingserver/grpc_gateways_test.go

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
3535
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
3636
"google.golang.org/grpc"
37+
"google.golang.org/protobuf/types/known/emptypb"
3738
)
3839

3940
var (
@@ -182,7 +183,9 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
182183
CallOpt grpc.CallOption
183184
ClaimFunc func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error)
184185
CreateFunc func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error)
186+
UpdateFunc func(context.Context, *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error)
185187
UnclaimFunc func(context.Context, types.EUI64) error
188+
DeleteFunc func(context.Context, *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error)
186189
ErrorAssertion func(error) bool
187190
}{
188191
{
@@ -241,6 +244,25 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
241244
CallOpt: authorizedCallOpt,
242245
ErrorAssertion: errors.IsAlreadyExists,
243246
},
247+
{
248+
Name: "Claim/GatewayCreationFailed",
249+
Req: &ttnpb.ClaimGatewayRequest{
250+
Collaborator: userID.GetOrganizationOrUserIdentifiers(),
251+
SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{
252+
AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{
253+
GatewayEui: supportedEUI.Bytes(),
254+
AuthenticationCode: claimAuthCode,
255+
},
256+
},
257+
TargetGatewayId: "test-gateway",
258+
TargetGatewayServerAddress: "things.example.com",
259+
},
260+
CallOpt: authorizedCallOpt,
261+
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
262+
return nil, errCreate.New()
263+
},
264+
ErrorAssertion: errors.IsAborted,
265+
},
244266
{
245267
Name: "Claim/EUINotRegisteredForClaiming",
246268
Req: &ttnpb.ClaimGatewayRequest{
@@ -254,7 +276,13 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
254276
TargetGatewayId: "test-gateway",
255277
TargetGatewayServerAddress: "things.example.com",
256278
},
257-
CallOpt: authorizedCallOpt,
279+
CallOpt: authorizedCallOpt,
280+
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
281+
return in.Gateway, nil
282+
},
283+
DeleteFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
284+
return &emptypb.Empty{}, nil
285+
},
258286
ErrorAssertion: errors.IsAborted,
259287
},
260288
{
@@ -271,13 +299,22 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
271299
TargetGatewayServerAddress: "things.example.com",
272300
},
273301
CallOpt: authorizedCallOpt,
302+
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
303+
return in.Gateway, nil
304+
},
274305
ClaimFunc: func(_ context.Context, _ types.EUI64, _, _ string) (*dcstypes.GatewayMetadata, error) {
275306
return nil, errClaim.New()
276307
},
308+
UpdateFunc: func(_ context.Context, in *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error) {
309+
return in.Gateway, nil
310+
},
311+
DeleteFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
312+
return &emptypb.Empty{}, nil
313+
},
277314
ErrorAssertion: errors.IsAborted,
278315
},
279316
{
280-
Name: "Claim/CreateFailed",
317+
Name: "Claim/UpdateFailed",
281318
Req: &ttnpb.ClaimGatewayRequest{
282319
Collaborator: userID.GetOrganizationOrUserIdentifiers(),
283320
SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{
@@ -294,7 +331,13 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
294331
return &dcstypes.GatewayMetadata{}, nil
295332
},
296333
CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
297-
return nil, errCreate.New()
334+
return nil, nil
335+
},
336+
UpdateFunc: func(_ context.Context, in *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error) {
337+
return nil, errUpdate.New()
338+
},
339+
DeleteFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
340+
return &emptypb.Empty{}, nil
298341
},
299342
UnclaimFunc: func(_ context.Context, eui types.EUI64) error {
300343
if eui.Equal(supportedEUI) {
@@ -305,7 +348,7 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
305348
ErrorAssertion: errors.IsAborted,
306349
},
307350
{
308-
Name: "Claim/CreateFailedWithUnclaimFailed",
351+
Name: "Claim/UpdateFailedWithUnclaimFailed",
309352
Req: &ttnpb.ClaimGatewayRequest{
310353
Collaborator: userID.GetOrganizationOrUserIdentifiers(),
311354
SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{
@@ -322,15 +365,21 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
322365
return &dcstypes.GatewayMetadata{}, nil
323366
},
324367
CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
325-
return nil, errCreate.New()
368+
return nil, nil
369+
},
370+
UpdateFunc: func(_ context.Context, in *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error) {
371+
return nil, errUpdate.New()
372+
},
373+
DeleteFunc: func(_ context.Context, _ *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) {
374+
return &emptypb.Empty{}, nil
326375
},
327376
UnclaimFunc: func(context.Context, types.EUI64) error {
328377
return errUnclaim.New()
329378
},
330379
ErrorAssertion: errors.IsAborted,
331380
},
332381
{
333-
Name: "Claim/SuccessfullyClaimedAndCreated",
382+
Name: "Claim/SuccessfullyClaimedAndUpdated",
334383
Req: &ttnpb.ClaimGatewayRequest{
335384
Collaborator: userID.GetOrganizationOrUserIdentifiers(),
336385
SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{
@@ -348,6 +397,9 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
348397
CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) {
349398
return in.Gateway, nil
350399
},
400+
UpdateFunc: func(_ context.Context, in *ttnpb.UpdateGatewayRequest) (*ttnpb.Gateway, error) {
401+
return in.Gateway, nil
402+
},
351403
CallOpt: authorizedCallOpt,
352404
},
353405
} {
@@ -361,6 +413,12 @@ func TestGatewayClaimingServer(t *testing.T) { //nolint:paralleltest
361413
if tc.CreateFunc != nil {
362414
mockGatewayRegistry.createFunc = tc.CreateFunc
363415
}
416+
if tc.UpdateFunc != nil {
417+
mockGatewayRegistry.updateFunc = tc.UpdateFunc
418+
}
419+
if tc.DeleteFunc != nil {
420+
mockGatewayRegistry.deleteFunc = tc.DeleteFunc
421+
}
364422

365423
_, err := gclsClient.Claim(ctx, tc.Req, tc.CallOpt)
366424
if err != nil {

pkg/deviceclaimingserver/types/types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ func RangeFromEUI64Range(start, end types.EUI64) EUI64Range {
6262

6363
// GatewayMetadata contains metadata of a gateway, typically returned on claiming.
6464
type GatewayMetadata struct {
65-
Antennas []*ttnpb.GatewayAntenna
65+
Antennas []*ttnpb.GatewayAntenna
66+
LBSLNSKey *ttnpb.APIKey
6667
}

pkg/deviceclaimingserver/util_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ var (
121121
errGatewayNotFound = errors.DefineNotFound("gateway_not_found", "gateway not found")
122122
errClaim = errors.DefineAborted("claim", "claim")
123123
errCreate = errors.DefineAborted("create_gateway", "create gateway")
124+
errUpdate = errors.DefineAborted("update_gateway", "update gateway")
124125
errUnclaim = errors.DefineAborted("unclaim", "unclaim gateway")
125126
)
126127

0 commit comments

Comments
 (0)