From 9aa559b94da8506e431151ebebe0863b4c7b4920 Mon Sep 17 00:00:00 2001 From: Johan Stokking Date: Thu, 22 Aug 2024 20:33:48 +0200 Subject: [PATCH 1/2] gs: Fix log namespace and increase log level for failed connects --- pkg/gatewayserver/io/ttigw/ttigw.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/gatewayserver/io/ttigw/ttigw.go b/pkg/gatewayserver/io/ttigw/ttigw.go index df083ad433..c870eefb8a 100644 --- a/pkg/gatewayserver/io/ttigw/ttigw.go +++ b/pkg/gatewayserver/io/ttigw/ttigw.go @@ -64,6 +64,8 @@ var _ io.Frontend = (*Frontend)(nil) // New returns a new The Things Industries V1 gateway frontend. func New(ctx context.Context, server io.Server, cfg Config) (*Frontend, error) { + ctx = log.NewContextWithField(ctx, "namespace", "gatewayserver/io/ttigw") + var proxyConfiguration webmiddleware.ProxyConfiguration if err := proxyConfiguration.ParseAndAddTrusted(server.GetBaseConfig(ctx).HTTP.TrustedProxies...); err != nil { return nil, err @@ -130,7 +132,7 @@ func (f *Frontend) handleGet(w http.ResponseWriter, r *http.Request) { } ctx, ids, err := f.authenticate(ctx, cert) if err != nil { - logger.WithError(err).Debug("Client certificate verification failed") + logger.WithError(err).Warn("Client certificate verification failed") writeError(w, err) return } @@ -140,7 +142,7 @@ func (f *Frontend) handleGet(w http.ResponseWriter, r *http.Request) { Ip: remoteIP(r), }) if err != nil { - logger.WithError(err).Info("Failed to connect") + logger.WithError(err).Warn("Failed to connect") writeError(w, err) return } From 513fa474a4698938a7c6c11ce8a603bc6d72dcab Mon Sep 17 00:00:00 2001 From: Johan Stokking Date: Fri, 23 Aug 2024 20:37:08 +0200 Subject: [PATCH 2/2] dcs: Set last known location on claim --- go.mod | 2 +- go.sum | 4 +- pkg/deviceclaimingserver/gateways/gateways.go | 4 +- .../gateways/ttgc/ttgc.go | 47 +++++++++++++++---- pkg/deviceclaimingserver/grpc_gateways.go | 4 +- .../grpc_gateways_test.go | 28 +++++------ pkg/deviceclaimingserver/types/types.go | 10 +++- pkg/deviceclaimingserver/util_test.go | 5 +- tools/go.mod | 2 +- tools/go.sum | 4 +- 10 files changed, 75 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index c1016b92c1..611e37919b 100644 --- a/go.mod +++ b/go.mod @@ -88,7 +88,7 @@ require ( go.packetbroker.org/api/mapping/v2 v2.3.2 go.packetbroker.org/api/routing v1.9.2 go.packetbroker.org/api/v3 v3.17.1 - go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd + go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240823134410-68cd7baab6c5 go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df go.thethings.network/lorawan-application-payload v0.0.0-20220125153912-1198ff1e403e go.thethings.network/lorawan-stack-legacy/v2 v2.1.0 diff --git a/go.sum b/go.sum index 036b68caea..8469345c14 100644 --- a/go.sum +++ b/go.sum @@ -658,8 +658,8 @@ go.packetbroker.org/api/routing v1.9.2 h1:J4+4vYZxa60UWC70Y9yy7sktU7DXaAp9Q13Bfq go.packetbroker.org/api/routing v1.9.2/go.mod h1:kd2K7gieDI35YfPA8/zDmLX3qiKPuXia/MA77BEAeUA= go.packetbroker.org/api/v3 v3.17.1 h1:LcyFPUGqVubGWMvQ16tZlQIKd+noGx7urzEYhSLiEQA= go.packetbroker.org/api/v3 v3.17.1/go.mod h1:6bVbdWAYLnvZ5kgXxA7GBQvZTN7vxI0DoF1Di1NoAT4= -go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd h1:FvD516hdD/iWqoS20SFdcoiUgwRJP3egNXNiN+Ux2d0= -go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd/go.mod h1:2+WsMwIunNLh22oauBzGL56JazE3UY34W1fstqEbacw= +go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240823134410-68cd7baab6c5 h1:w4NvcBAq7JCwlfthvGw1PRWuI6pCf5TlIjKgFHKml8M= +go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240823134410-68cd7baab6c5/go.mod h1:2+WsMwIunNLh22oauBzGL56JazE3UY34W1fstqEbacw= go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df h1:IMSctPllwEtJLyM/1AWgBiN1NPwBKqEVkCiK0I/MNY4= go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df/go.mod h1:89OU623VYKW9i3W4CZgIGFmtgb/jsN8JV2PAuCsj+7w= go.thethings.network/lorawan-application-payload v0.0.0-20220125153912-1198ff1e403e h1:TWGQ3lh7gI2W5hnb6qPdpoAa0d7s/XPwvgf2VVCMJaY= diff --git a/pkg/deviceclaimingserver/gateways/gateways.go b/pkg/deviceclaimingserver/gateways/gateways.go index 2428e27dde..00033fd056 100644 --- a/pkg/deviceclaimingserver/gateways/gateways.go +++ b/pkg/deviceclaimingserver/gateways/gateways.go @@ -85,7 +85,9 @@ func ParseGatewayEUIRanges(conf map[string][]string) (map[string][]dcstypes.EUI6 // Claimer provides methods for claiming Gateways. type Claimer interface { // Claim claims a gateway. - Claim(ctx context.Context, eui types.EUI64, ownerToken string, clusterAddress string) error + Claim( + ctx context.Context, eui types.EUI64, ownerToken string, clusterAddress string, + ) (*dcstypes.GatewayMetadata, error) // Unclaim unclaims a gateway. Unclaim(ctx context.Context, eui types.EUI64) error // IsManagedGateway returns true if the gateway is a managed gateway. diff --git a/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go b/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go index cb687eeca4..8b35fbd695 100644 --- a/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go +++ b/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go @@ -23,8 +23,11 @@ import ( northboundv1 "go.thethings.industries/pkg/api/gen/tti/gateway/controller/northbound/v1" "go.thethings.network/lorawan-stack/v3/pkg/config/tlsconfig" + dcstypes "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/types" + "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/log" "go.thethings.network/lorawan-stack/v3/pkg/ttgc" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -61,7 +64,9 @@ func New(ctx context.Context, c ttgc.Component, config ttgc.Config) (*Upstream, // 2. Upsert a LoRa Packet Forwarder profile with the root CA presented by the given Gateway Server // 3. Upsert a Geolocation profile // 4. Update the gateway with the profiles -func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clusterAddress string) error { +func (u *Upstream) Claim( + ctx context.Context, eui types.EUI64, ownerToken, clusterAddress string, +) (*dcstypes.GatewayMetadata, error) { logger := log.FromContext(ctx) // Claim the gateway. @@ -72,7 +77,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust OwnerToken: ownerToken, }) if err != nil { - return err + return nil, err } // Get the root CA from the Gateway Server and upsert the LoRa Packet Forwarder profile. @@ -83,7 +88,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust clusterAddress = net.JoinHostPort(host, "8889") rootCA, err := u.getRootCA(ctx, clusterAddress) if err != nil { - return err + return nil, err } var ( loraPFProfileID []byte @@ -107,7 +112,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust if err != nil { if status.Code(err) != codes.NotFound { logger.WithError(err).Warn("Failed to get LoRa Packet Forwarder profile") - return err + return nil, err } res, err := loraPFProfileClient.Create(ctx, &northboundv1.LoraPacketForwarderProfileServiceCreateRequest{ Domain: u.client.Domain(ctx), @@ -116,7 +121,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust }) if err != nil { logger.WithError(err).Warn("Failed to create LoRa Packet Forwarder profile") - return err + return nil, err } loraPFProfileID = res.ProfileId } else { @@ -131,7 +136,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust }) if err != nil { logger.WithError(err).Warn("Failed to update LoRa Packet Forwarder profile") - return err + return nil, err } } loraPFProfileID = loraPFGetRes.ProfileId @@ -158,7 +163,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust if err != nil { if status.Code(err) != codes.NotFound { logger.WithError(err).Warn("Failed to get geolocation profile") - return err + return nil, err } res, err := geolocationProfileClient.Create(ctx, &northboundv1.GeolocationProfileServiceCreateRequest{ Domain: u.client.Domain(ctx), @@ -167,7 +172,7 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust }) if err != nil { logger.WithError(err).Warn("Failed to create geolocation profile") - return err + return nil, err } geolocationProfileID = res.ProfileId } else { @@ -187,10 +192,29 @@ func (u *Upstream) Claim(ctx context.Context, eui types.EUI64, ownerToken, clust }) if err != nil { logger.WithError(err).Warn("Failed to update gateway with profiles") - return err + return nil, err } - return nil + gatewayMetadata := &dcstypes.GatewayMetadata{} + locationRes, err := gtwClient.GetLastLocation(ctx, &northboundv1.GatewayServiceGetLastLocationRequest{ + GatewayId: eui.MarshalNumber(), + Domain: u.client.Domain(ctx), + }) + if err != nil && !errors.IsNotFound(err) { + logger.WithError(err).Warn("Failed to get gateway location") + } else if err == nil { + gatewayMetadata.Antennas = []*ttnpb.GatewayAntenna{ + { + Location: &ttnpb.Location{ + Latitude: locationRes.Location.Latitude, + Longitude: locationRes.Location.Longitude, + Accuracy: int32(locationRes.Location.Accuracy), + }, + }, + } + } + + return gatewayMetadata, nil } // Unclaim implements gateways.GatewayClaimer. @@ -201,6 +225,9 @@ func (u *Upstream) Unclaim(ctx context.Context, eui types.EUI64) error { Domain: u.client.Domain(ctx), }) if err != nil { + if errors.IsNotFound(err) { // The gateway does not exist or is already unclaimed. + return nil + } return err } return nil diff --git a/pkg/deviceclaimingserver/grpc_gateways.go b/pkg/deviceclaimingserver/grpc_gateways.go index dc5ff8f93c..3b93150ee8 100644 --- a/pkg/deviceclaimingserver/grpc_gateways.go +++ b/pkg/deviceclaimingserver/grpc_gateways.go @@ -119,7 +119,8 @@ func (gcls *gatewayClaimingServer) Claim( } // Claim the gateway on the upstream. - if err := claimer.Claim(ctx, gatewayEUI, string(authCode), req.TargetGatewayServerAddress); err != nil { + res, err := claimer.Claim(ctx, gatewayEUI, string(authCode), req.TargetGatewayServerAddress) + if err != nil { observability.RegisterFailClaim(ctx, ids.GetEntityIdentifiers(), err) return nil, errClaim.WithCause(err) } @@ -143,6 +144,7 @@ func (gcls *gatewayClaimingServer) Claim( EnforceDutyCycle: true, RequireAuthenticatedConnection: true, FrequencyPlanIds: req.TargetFrequencyPlanIds, + Antennas: res.Antennas, } _, err = gcls.registry.Create(ctx, &ttnpb.CreateGatewayRequest{ diff --git a/pkg/deviceclaimingserver/grpc_gateways_test.go b/pkg/deviceclaimingserver/grpc_gateways_test.go index d25864ce61..baf104822a 100644 --- a/pkg/deviceclaimingserver/grpc_gateways_test.go +++ b/pkg/deviceclaimingserver/grpc_gateways_test.go @@ -71,7 +71,7 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest }, }) - mockGatewayclaimer := &MockGatewayClaimer{ + mockGatewayClaimer := &MockGatewayClaimer{ IsManagedGatewayFunc: func(_ context.Context, e types.EUI64) (bool, error) { return e.Equal(supportedEUI), nil }, @@ -88,7 +88,7 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest Length: 48, }), }, - mockGatewayclaimer, + mockGatewayClaimer, ), ) if err != nil { @@ -180,7 +180,7 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest Name string Req *ttnpb.ClaimGatewayRequest CallOpt grpc.CallOption - ClaimFunc func(context.Context, types.EUI64, string, string) error + ClaimFunc func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) CreateFunc func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) UnclaimFunc func(context.Context, types.EUI64) error ErrorAssertion func(error) bool @@ -271,8 +271,8 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest TargetGatewayServerAddress: "things.example.com", }, CallOpt: authorizedCallOpt, - ClaimFunc: func(_ context.Context, _ types.EUI64, _, _ string) error { - return errClaim.New() + ClaimFunc: func(_ context.Context, _ types.EUI64, _, _ string) (*dcstypes.GatewayMetadata, error) { + return nil, errClaim.New() }, ErrorAssertion: errors.IsAborted, }, @@ -290,8 +290,8 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest TargetGatewayServerAddress: "things.example.com", }, CallOpt: authorizedCallOpt, - ClaimFunc: func(context.Context, types.EUI64, string, string) error { - return nil + ClaimFunc: func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) { + return &dcstypes.GatewayMetadata{}, nil }, CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { return nil, errCreate.New() @@ -318,8 +318,8 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest TargetGatewayServerAddress: "things.example.com", }, CallOpt: authorizedCallOpt, - ClaimFunc: func(context.Context, types.EUI64, string, string) error { - return nil + ClaimFunc: func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) { + return &dcstypes.GatewayMetadata{}, nil }, CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { return nil, errCreate.New() @@ -342,8 +342,8 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest TargetGatewayId: "test-gateway", TargetGatewayServerAddress: "things.example.com", }, - ClaimFunc: func(context.Context, types.EUI64, string, string) error { - return nil + ClaimFunc: func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) { + return &dcstypes.GatewayMetadata{}, nil }, CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { return in.Gateway, nil @@ -354,10 +354,10 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest tc := tc t.Run(tc.Name, func(t *testing.T) { if tc.ClaimFunc != nil { - mockGatewayclaimer.ClaimFunc = tc.ClaimFunc + mockGatewayClaimer.ClaimFunc = tc.ClaimFunc } if tc.UnclaimFunc != nil { - mockGatewayclaimer.UnclaimFunc = tc.UnclaimFunc + mockGatewayClaimer.UnclaimFunc = tc.UnclaimFunc } if tc.CreateFunc != nil { mockGatewayRegistry.createFunc = tc.CreateFunc @@ -480,7 +480,7 @@ func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest tc := tc t.Run(tc.Name, func(t *testing.T) { if tc.UnclaimFunc != nil { - mockGatewayclaimer.UnclaimFunc = tc.UnclaimFunc + mockGatewayClaimer.UnclaimFunc = tc.UnclaimFunc } if tc.GetFunc != nil { mockGatewayRegistry.getFunc = tc.GetFunc diff --git a/pkg/deviceclaimingserver/types/types.go b/pkg/deviceclaimingserver/types/types.go index 2e17c59454..e27cf94f92 100644 --- a/pkg/deviceclaimingserver/types/types.go +++ b/pkg/deviceclaimingserver/types/types.go @@ -15,7 +15,10 @@ // Package types provides types for the Device Claiming Server. package types -import "go.thethings.network/lorawan-stack/v3/pkg/types" +import ( + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "go.thethings.network/lorawan-stack/v3/pkg/types" +) // EUI64Range is a range of EUI64s. type EUI64Range interface { @@ -56,3 +59,8 @@ func RangeFromEUI64Range(start, end types.EUI64) EUI64Range { end: end.MarshalNumber(), } } + +// GatewayMetadata contains metadata of a gateway, typically returned on claiming. +type GatewayMetadata struct { + Antennas []*ttnpb.GatewayAntenna +} diff --git a/pkg/deviceclaimingserver/util_test.go b/pkg/deviceclaimingserver/util_test.go index 1c1dc94611..102190da55 100644 --- a/pkg/deviceclaimingserver/util_test.go +++ b/pkg/deviceclaimingserver/util_test.go @@ -17,6 +17,7 @@ package deviceclaimingserver_test import ( "context" + dcstypes "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/types" "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" @@ -80,7 +81,7 @@ func (m MockEndDeviceClaimer) BatchUnclaim( type MockGatewayClaimer struct { EUIs []types.EUI64 - ClaimFunc func(context.Context, types.EUI64, string, string) error + ClaimFunc func(context.Context, types.EUI64, string, string) (*dcstypes.GatewayMetadata, error) UnclaimFunc func(context.Context, types.EUI64) error IsManagedGatewayFunc func(context.Context, types.EUI64) (bool, error) } @@ -91,7 +92,7 @@ func (claimer MockGatewayClaimer) Claim( eui types.EUI64, ownerToken string, clusterAddress string, -) error { +) (*dcstypes.GatewayMetadata, error) { return claimer.ClaimFunc(ctx, eui, ownerToken, clusterAddress) } diff --git a/tools/go.mod b/tools/go.mod index e5ad280d37..5389035ec9 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -218,7 +218,7 @@ require ( go.packetbroker.org/api/mapping/v2 v2.3.2 // indirect go.packetbroker.org/api/routing v1.9.2 // indirect go.packetbroker.org/api/v3 v3.17.1 // indirect - go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd // indirect + go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240823134410-68cd7baab6c5 // indirect go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df // indirect go.thethings.network/lorawan-application-payload v0.0.0-20220125153912-1198ff1e403e // indirect go.thethings.network/lorawan-stack-legacy/v2 v2.1.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 67401a1d05..637122101d 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -713,8 +713,8 @@ go.packetbroker.org/api/routing v1.9.2 h1:J4+4vYZxa60UWC70Y9yy7sktU7DXaAp9Q13Bfq go.packetbroker.org/api/routing v1.9.2/go.mod h1:kd2K7gieDI35YfPA8/zDmLX3qiKPuXia/MA77BEAeUA= go.packetbroker.org/api/v3 v3.17.1 h1:LcyFPUGqVubGWMvQ16tZlQIKd+noGx7urzEYhSLiEQA= go.packetbroker.org/api/v3 v3.17.1/go.mod h1:6bVbdWAYLnvZ5kgXxA7GBQvZTN7vxI0DoF1Di1NoAT4= -go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd h1:FvD516hdD/iWqoS20SFdcoiUgwRJP3egNXNiN+Ux2d0= -go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240729145607-ea516688afbd/go.mod h1:2+WsMwIunNLh22oauBzGL56JazE3UY34W1fstqEbacw= +go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240823134410-68cd7baab6c5 h1:w4NvcBAq7JCwlfthvGw1PRWuI6pCf5TlIjKgFHKml8M= +go.thethings.industries/pkg/api/gen/tti/gateway v0.0.0-20240823134410-68cd7baab6c5/go.mod h1:2+WsMwIunNLh22oauBzGL56JazE3UY34W1fstqEbacw= go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df h1:IMSctPllwEtJLyM/1AWgBiN1NPwBKqEVkCiK0I/MNY4= go.thethings.industries/pkg/ca v0.0.0-20240809123127-21a24c0e47df/go.mod h1:89OU623VYKW9i3W4CZgIGFmtgb/jsN8JV2PAuCsj+7w= go.thethings.network/lorawan-application-payload v0.0.0-20220125153912-1198ff1e403e h1:TWGQ3lh7gI2W5hnb6qPdpoAa0d7s/XPwvgf2VVCMJaY=