From 075e701dd066102d412dce0e8a13c9b57720ce94 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Mon, 8 Sep 2025 15:45:48 +0100 Subject: [PATCH 01/13] cancel subscribe --- internal/command/command_plugin.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/command/command_plugin.go b/internal/command/command_plugin.go index e3315720b..51d78b0af 100644 --- a/internal/command/command_plugin.go +++ b/internal/command/command_plugin.go @@ -230,6 +230,25 @@ func (cp *CommandPlugin) processDataPlaneResponse(ctx context.Context, msg *bus. func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Message) { slog.DebugContext(ctx, "Command plugin received connection reset message") if newConnection, ok := msg.Data.(grpc.GrpcConnectionInterface); ok { + + slog.InfoContext(ctx, "Resetting Subscribe") + ctxWithMetadata := cp.config.NewContextWithLabels(ctx) + cp.subscribeMutex.Lock() + if cp.subscribeCancel != nil { + var subscribeCtx context.Context + + cp.subscribeCancel() + subscribeCtx, cp.subscribeCancel = context.WithCancel(ctxWithMetadata) + cp.subscribeMutex.Unlock() + go cp.commandService.Subscribe(subscribeCtx) + + slog.InfoContext(ctxWithMetadata, "Successfully Reset Subscribe") + } else { + cp.subscribeMutex.Unlock() + slog.InfoContext(ctxWithMetadata, "Unable to Reset Subscribe") + return + } + connectionErr := cp.conn.Close(ctx) if connectionErr != nil { slog.ErrorContext(ctx, "Command plugin: unable to close connection", "error", connectionErr) From 564d35e5e60692bf5d8b5d6948b2c350d94beb45 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Mon, 8 Sep 2025 15:49:12 +0100 Subject: [PATCH 02/13] cancel subscribe --- internal/command/command_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/command_plugin.go b/internal/command/command_plugin.go index 51d78b0af..54241a1ab 100644 --- a/internal/command/command_plugin.go +++ b/internal/command/command_plugin.go @@ -230,7 +230,6 @@ func (cp *CommandPlugin) processDataPlaneResponse(ctx context.Context, msg *bus. func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Message) { slog.DebugContext(ctx, "Command plugin received connection reset message") if newConnection, ok := msg.Data.(grpc.GrpcConnectionInterface); ok { - slog.InfoContext(ctx, "Resetting Subscribe") ctxWithMetadata := cp.config.NewContextWithLabels(ctx) cp.subscribeMutex.Lock() @@ -246,6 +245,7 @@ func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Me } else { cp.subscribeMutex.Unlock() slog.InfoContext(ctxWithMetadata, "Unable to Reset Subscribe") + return } From 5a53ef14021cf964298295efbd41eb0f40f46b31 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Tue, 9 Sep 2025 11:12:34 +0100 Subject: [PATCH 03/13] change order --- internal/command/command_plugin.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/internal/command/command_plugin.go b/internal/command/command_plugin.go index 54241a1ab..eb26ef1e5 100644 --- a/internal/command/command_plugin.go +++ b/internal/command/command_plugin.go @@ -228,37 +228,35 @@ func (cp *CommandPlugin) processDataPlaneResponse(ctx context.Context, msg *bus. } func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Message) { + var subscribeCtx context.Context slog.DebugContext(ctx, "Command plugin received connection reset message") + if newConnection, ok := msg.Data.(grpc.GrpcConnectionInterface); ok { slog.InfoContext(ctx, "Resetting Subscribe") ctxWithMetadata := cp.config.NewContextWithLabels(ctx) cp.subscribeMutex.Lock() - if cp.subscribeCancel != nil { - var subscribeCtx context.Context + defer cp.subscribeMutex.Unlock() + if cp.subscribeCancel != nil { cp.subscribeCancel() - subscribeCtx, cp.subscribeCancel = context.WithCancel(ctxWithMetadata) - cp.subscribeMutex.Unlock() - go cp.commandService.Subscribe(subscribeCtx) - slog.InfoContext(ctxWithMetadata, "Successfully Reset Subscribe") - } else { - cp.subscribeMutex.Unlock() - slog.InfoContext(ctxWithMetadata, "Unable to Reset Subscribe") - - return } - + connectionErr := cp.conn.Close(ctx) if connectionErr != nil { slog.ErrorContext(ctx, "Command plugin: unable to close connection", "error", connectionErr) } + cp.conn = newConnection err := cp.commandService.UpdateClient(ctx, cp.conn.CommandServiceClient()) if err != nil { slog.ErrorContext(ctx, "Failed to reset connection", "error", err) return } + + subscribeCtx, cp.subscribeCancel = context.WithCancel(ctxWithMetadata) + go cp.commandService.Subscribe(subscribeCtx) + slog.DebugContext(ctx, "Command service client reset successfully") } } From 1bef9f1ed38d898a2507ac3be125a1b9896c3362 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Tue, 9 Sep 2025 12:12:43 +0100 Subject: [PATCH 04/13] change order --- internal/command/command_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/command_plugin.go b/internal/command/command_plugin.go index eb26ef1e5..bb52e65e6 100644 --- a/internal/command/command_plugin.go +++ b/internal/command/command_plugin.go @@ -241,7 +241,7 @@ func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Me cp.subscribeCancel() slog.InfoContext(ctxWithMetadata, "Successfully Reset Subscribe") } - + connectionErr := cp.conn.Close(ctx) if connectionErr != nil { slog.ErrorContext(ctx, "Command plugin: unable to close connection", "error", connectionErr) From 1744946185684deec50bb22eab13371725b2ecd3 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Wed, 10 Sep 2025 17:27:33 +0100 Subject: [PATCH 05/13] try this --- api/grpc/mpi/v1/command.pb.go | 2 +- api/grpc/mpi/v1/common.pb.go | 2 +- api/grpc/mpi/v1/files.pb.go | 2 +- internal/file/file_manager_service.go | 13 ++ internal/file/file_operator.go | 1 + internal/file/file_plugin.go | 3 +- internal/file/file_service_operator.go | 4 + .../fake_file_manager_service_interface.go | 39 ++++++ internal/grpc/grpc.go | 4 + test.txt | 119 ++++++++++++++++++ .../mock/grpc/mock_management_file_service.go | 10 ++ 11 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 test.txt diff --git a/api/grpc/mpi/v1/command.pb.go b/api/grpc/mpi/v1/command.pb.go index 39ef93489..b5f0346f4 100644 --- a/api/grpc/mpi/v1/command.pb.go +++ b/api/grpc/mpi/v1/command.pb.go @@ -8,7 +8,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: mpi/v1/command.proto diff --git a/api/grpc/mpi/v1/common.pb.go b/api/grpc/mpi/v1/common.pb.go index 890c2b073..e6d06cf37 100644 --- a/api/grpc/mpi/v1/common.pb.go +++ b/api/grpc/mpi/v1/common.pb.go @@ -5,7 +5,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: mpi/v1/common.proto diff --git a/api/grpc/mpi/v1/files.pb.go b/api/grpc/mpi/v1/files.pb.go index 1873a19e2..d379ed70a 100644 --- a/api/grpc/mpi/v1/files.pb.go +++ b/api/grpc/mpi/v1/files.pb.go @@ -5,7 +5,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: mpi/v1/files.proto diff --git a/internal/file/file_manager_service.go b/internal/file/file_manager_service.go index 869284492..791cd1ad4 100644 --- a/internal/file/file_manager_service.go +++ b/internal/file/file_manager_service.go @@ -68,6 +68,7 @@ type ( fileToUpdate *mpi.File, ) error SetIsConnected(isConnected bool) + UpdateClient(fileServiceClient mpi.FileServiceClient) } fileManagerServiceInterface interface { @@ -85,6 +86,7 @@ type ( ) (map[string]*model.FileCache, map[string][]byte, error) IsConnected() bool SetIsConnected(isConnected bool) + ResetClient(fileServiceClient mpi.FileServiceClient) } ) @@ -120,6 +122,10 @@ func NewFileManagerService(fileServiceClient mpi.FileServiceClient, agentConfig } } +func (fms *FileManagerService) ResetClient(fileServiceClient mpi.FileServiceClient) { + fms.fileServiceOperator.UpdateClient(fileServiceClient) +} + func (fms *FileManagerService) IsConnected() bool { return fms.fileServiceOperator.IsConnected() } @@ -212,6 +218,8 @@ func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string) } } + slog.InfoContext(ctx, "Rolling back config for instance, writing manifest file", + "manifest_previous", fms.previousManifestFiles) manifestFileErr := fms.fileOperator.WriteManifestFile(fms.previousManifestFiles, fms.agentConfig.ManifestDir, fms.manifestFilePath) if manifestFileErr != nil { @@ -387,6 +395,11 @@ func (fms *FileManagerService) UpdateCurrentFilesOnDisk( func (fms *FileManagerService) UpdateManifestFile(currentFiles map[string]*mpi.File, referenced bool) (err error) { slog.Debug("Updating manifest file", "current_files", currentFiles, "referenced", referenced) currentManifestFiles, _, readError := fms.manifestFile() + + if len(currentManifestFiles) == 0 { + currentManifestFiles = fms.convertToManifestFileMap(currentFiles, referenced) + } + fms.previousManifestFiles = currentManifestFiles if readError != nil && !errors.Is(readError, os.ErrNotExist) { slog.Debug("Error reading manifest file", "current_manifest_files", diff --git a/internal/file/file_operator.go b/internal/file/file_operator.go index ee65925c8..f65de514c 100644 --- a/internal/file/file_operator.go +++ b/internal/file/file_operator.go @@ -152,6 +152,7 @@ func (fo *FileOperator) ReadChunk( func (fo *FileOperator) WriteManifestFile(updatedFiles map[string]*model.ManifestFile, manifestDir, manifestPath string, ) (writeError error) { + slog.Info("Writing manifest file") manifestJSON, err := json.MarshalIndent(updatedFiles, "", " ") if err != nil { return fmt.Errorf("unable to marshal manifest file json: %w", err) diff --git a/internal/file/file_plugin.go b/internal/file/file_plugin.go index c093fe62e..6eedf6fff 100644 --- a/internal/file/file_plugin.go +++ b/internal/file/file_plugin.go @@ -149,7 +149,8 @@ func (fp *FilePlugin) handleConnectionReset(ctx context.Context, msg *bus.Messag fp.conn = newConnection reconnect = fp.fileManagerService.IsConnected() - fp.fileManagerService = NewFileManagerService(fp.conn.FileServiceClient(), fp.config, fp.manifestLock) + fp.fileManagerService.ResetClient(fp.conn.FileServiceClient()) + // fp.fileManagerService = NewFileManagerService(fp.conn.FileServiceClient(), fp.config, fp.manifestLock) fp.fileManagerService.SetIsConnected(reconnect) slog.DebugContext(ctx, "File manager service client reset successfully") diff --git a/internal/file/file_service_operator.go b/internal/file/file_service_operator.go index 92931c430..90b0179cb 100644 --- a/internal/file/file_service_operator.go +++ b/internal/file/file_service_operator.go @@ -56,6 +56,10 @@ func NewFileServiceOperator(agentConfig *config.Config, fileServiceClient mpi.Fi } } +func (fso *FileServiceOperator) UpdateClient(fileServiceClient mpi.FileServiceClient) { + fso.fileServiceClient = fileServiceClient +} + func (fso *FileServiceOperator) SetIsConnected(isConnected bool) { fso.isConnected.Store(isConnected) } diff --git a/internal/file/filefakes/fake_file_manager_service_interface.go b/internal/file/filefakes/fake_file_manager_service_interface.go index 3583dd166..2c1b8ed5d 100644 --- a/internal/file/filefakes/fake_file_manager_service_interface.go +++ b/internal/file/filefakes/fake_file_manager_service_interface.go @@ -73,6 +73,11 @@ type FakeFileManagerServiceInterface struct { isConnectedReturnsOnCall map[int]struct { result1 bool } + ResetClientStub func(v1.FileServiceClient) + resetClientMutex sync.RWMutex + resetClientArgsForCall []struct { + arg1 v1.FileServiceClient + } RollbackStub func(context.Context, string) error rollbackMutex sync.RWMutex rollbackArgsForCall []struct { @@ -413,6 +418,38 @@ func (fake *FakeFileManagerServiceInterface) IsConnectedReturnsOnCall(i int, res }{result1} } +func (fake *FakeFileManagerServiceInterface) ResetClient(arg1 v1.FileServiceClient) { + fake.resetClientMutex.Lock() + fake.resetClientArgsForCall = append(fake.resetClientArgsForCall, struct { + arg1 v1.FileServiceClient + }{arg1}) + stub := fake.ResetClientStub + fake.recordInvocation("ResetClient", []interface{}{arg1}) + fake.resetClientMutex.Unlock() + if stub != nil { + fake.ResetClientStub(arg1) + } +} + +func (fake *FakeFileManagerServiceInterface) ResetClientCallCount() int { + fake.resetClientMutex.RLock() + defer fake.resetClientMutex.RUnlock() + return len(fake.resetClientArgsForCall) +} + +func (fake *FakeFileManagerServiceInterface) ResetClientCalls(stub func(v1.FileServiceClient)) { + fake.resetClientMutex.Lock() + defer fake.resetClientMutex.Unlock() + fake.ResetClientStub = stub +} + +func (fake *FakeFileManagerServiceInterface) ResetClientArgsForCall(i int) v1.FileServiceClient { + fake.resetClientMutex.RLock() + defer fake.resetClientMutex.RUnlock() + argsForCall := fake.resetClientArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeFileManagerServiceInterface) Rollback(arg1 context.Context, arg2 string) error { fake.rollbackMutex.Lock() ret, specificReturn := fake.rollbackReturnsOnCall[len(fake.rollbackArgsForCall)] @@ -585,6 +622,8 @@ func (fake *FakeFileManagerServiceInterface) Invocations() map[string][][]interf defer fake.determineFileActionsMutex.RUnlock() fake.isConnectedMutex.RLock() defer fake.isConnectedMutex.RUnlock() + fake.resetClientMutex.RLock() + defer fake.resetClientMutex.RUnlock() fake.rollbackMutex.RLock() defer fake.rollbackMutex.RUnlock() fake.setIsConnectedMutex.RLock() diff --git a/internal/grpc/grpc.go b/internal/grpc/grpc.go index 4fb036187..a7ed75c67 100644 --- a/internal/grpc/grpc.go +++ b/internal/grpc/grpc.go @@ -231,6 +231,10 @@ func DialOptions(agentConfig *config.Config, commandConfig *config.Command, reso opts = addPerRPCCredentials(commandConfig, resourceID, opts) } + // opts = append(opts, + // grpc.WithTransportCredentials(defaultCredentials), + // ) + return opts } diff --git a/test.txt b/test.txt new file mode 100644 index 000000000..bb0e3e628 --- /dev/null +++ b/test.txt @@ -0,0 +1,119 @@ + +time=2025-09-09T15:13:02.846Z level=DEBUG source=command/command_plugin.go:274 msg="Received management plane request" request="message_meta:{message_id:\\"3a11dc25-267c-41d8-87f9-7b7cdd2863bc\\" correlation_id:\\"3ac7c2ef-a756-4820-b407-e94f64531869\\" timestamp:{seconds:1757430782 nanos:842281487}} +config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} +files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"gzh1NUznNQjgHWysBFcM0U4qepgzAtGoXDkYREx47IQ=\\" permissions:\\"0644\\" size:5419}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} +files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} +files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} +config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"nF8he2FHbzLuMMvHjvek3OiM8dQmNmOu6j2U8O1ILus=\\"}}}" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' + + +'time=2025-09-09T15:13:03.319Z level=INFO source=command/command_plugin.go:221 msg="Sending data plane response message" message="Config apply successful" status=COMMAND_STATUS_OK correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' + + +'time=2025-09-09T15:13:03.319Z level=DEBUG source=command/command_service.go:169 msg="Sending data plane response" response="message_meta:{message_id:\\"7b105f0f-8d8f-11f0-8e45-f69b220a4435\\" correlation_id:\\"3ac7c2ef-a756-4820-b407-e94f64531869\\" timestamp:{seconds:1757430783 nanos:299561456}} +command_response:{status:COMMAND_STATUS_OK message:\\"Config apply successful\\"} instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\"" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' + + +-------------- + +'time=2025-09-09T15:13:03.321Z level=DEBUG source=command/command_service.go:364 msg="Removed config apply requests from queue" queue=[] correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 --- Think this is just a debug that should not be there + +token reset + + +'time=2025-09-09T15:13:03.324Z level=DEBUG source=file/file_plugin.go:354 msg="File plugin received nginx config update message" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' +time=2025-09-09T15:13:03.395Z level=DEBUG source=file/file_service_operator.go:174 msg="UpdateOverview response" response="" correlation_id=7b149162-8d8f-11f0-8e45-f69b220a4435 server_type=command' +'time=2025-09-09T15:13:03.396Z level=DEBUG source=file/file_service_operator.go:177 msg="UpdateOverview response is empty" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' + + + +------- + + + + +time=2025-09-09T15:13:03.397Z level=DEBUG source=command/command_plugin.go:274 msg="Received management plane request" request="message_meta:{message_id:\\"fc8d4120-1a11-4780-abb6-f9c94466a49d\\" correlation_id:\\"e0ca9bc8-9bdd-42b5-be15-2a64f543c555\\" timestamp:{seconds:1757430783 nanos:336746495}} +config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} +files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} +files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} +files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} +config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}" correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' + + +'time=2025-09-09T15:13:03.402Z level=DEBUG source=file/file_service_operator.go:70 msg="Getting file" file=/etc/nginx/conf.d/http.conf correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 + + + +'time=2025-09-09T15:13:03.406Z level=DEBUG source=watcher/watcher_plugin.go:311 msg="Received credential update event for command server" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' +'time=2025-09-09T15:13:03.407Z level=DEBUG source=file/file_plugin.go:142 msg="File plugin received connection reset message" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' +'time=2025-09-09T15:13:03.407Z level=INFO source=grpc/grpc.go:129 msg="Closing grpc connection" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' +'time=2025-09-09T15:13:03.409Z level=ERROR source=command/command_service.go:468 msg="Failed toreceive message from subscribe stream" error="rpc error: code = Canceled desc = context canceled" correlation_id=4b012305-8d8e-11f0-8e45-f69b220a4435 server_type=command' +'time=2025-09-09T15:13:03.409Z level=WARN source=command/command_service.go:201 msg="Failed to receive messages from subscribe stream" error="context canceled" correlation_id=4b012305-8d8e-11f0-8e45-f69b220a4435 server_type=command' +'time=2025-09-09T15:13:03.413Z level=DEBUG source=file/file_plugin.go:155 msg="File manager service client reset successfully" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' + + +'time=2025-09-09T15:13:03.413Z level=DEBUG source=command/command_service.go:237 msg="Sending create connection request" request="message_meta:{message_id:\\"7b21d4ef-8d8f-11f0-8e45-f69b220a4435\\" correlation_id:\\"7afcd968-8d8f-11f0-8e45-f69b220a4435\\" timestamp:{seconds:1757430783 nanos:413990004}}" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' + +'time=2025-09-09T15:13:03.820Z level=INFO source=command/command_service.go:246 msg="Connection created" response=response:{status:COMMAND_STATUS_OK} correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' + +'time=2025-09-09T15:13:03.820Z level=INFO source=command/command_service.go:247 msg="Agent connected" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' + + +'time=2025-09-09T15:13:04.907Z level=DEBUG source=command/command_service.go:482 msg="Config apply request is already in progress, queuing new config apply request" request="message_meta:{message_id:\\"1d1f86d3-195d-4e17-8250-2e0f5511ffd3\\" correlation_id:\\"2f2ad4fd-d210-4be2-83cf-51069099dfb1\\" timestamp:{seconds:1757430784 nanos:844163401}} +config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} +files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} +files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} +files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} +config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' + + +'time=2025-09-09T15:13:48.969Z level=ERROR source=file/file_plugin.go:296 msg="Failed to apply config changes, rolling back" instance_id=e8d1bda6-397e-3b98-a179-e500ff99fbc7 error="error getting file data for name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" +permissions:\\"0644\\" size:5390: rpc error: code = Canceled desc = grpc: the client connection is closing" correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' + + 'time=2025-09-09T15:13:48.970Z level=DEBUG source=command/command_service.go:169 msg="Sending data plane response" response="message_meta:{message_id:\\"96490f35-8d8f-11f0-8e45-f69b220a4435\\" correlation_id:\\"e0ca9bc8-9bdd-42b5-be15-2a64f543c555\\" + timestamp:{seconds:1757430828 nanos:969555159}} command_response:{status:COMMAND_STATUS_ERROR message:\\"Config apply failed, rolling back config\\" + error:\\"error getting file data for name:\\\\\\"/etc/nginx/conf.d/http.conf\\\\\\" hash:\\\\\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\\\\\" permissions:\\\\\\"0644\\\\\\" size:5390: rpc error: code = Canceled desc = grpc: the client connection is closing\\"} instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\"" + correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' + + + + +'time=2025-09-09T15:13:48.970Z level=DEBUG source=command/command_service.go:364 msg="Removed config apply requests from queue" queue="[message_meta:{message_id:\\"1d1f86d3-195d-4e17-8250-2e0f5511ffd3\\" correlation_id:\\"2f2ad4fd-d210-4be2-83cf-51069099dfb1\\" timestamp:{seconds:1757430784 nanos:844163401}} +config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} +files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} +files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} +files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} +config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}]" correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' + +'time=2025-09-09T15:13:48.971Z level=INFO source=command/command_plugin.go:221 msg="Sending data plane response message" message="Config apply failed, rollback successful" status=COMMAND_STATUS_FAILURE correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' + + + + + +time=2025-09-09T15:13:48.971Z level=DEBUG source=command/command_plugin.go:274 msg="Received management plane request" request="message_meta:{message_id:\\"1d1f86d3-195d-4e17-8250-2e0f5511ffd3\\" correlation_id:\\"2f2ad4fd-d210-4be2-83cf-51069099dfb1\\" timestamp:{seconds:1757430784 nanos:844163401}} +config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} +files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} +files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} +files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} +files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} +config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}" correlation_id=2f2ad4fd-d210-4be2-83cf-51069099dfb1 server_type=command + +'time=2025-09-09T15:13:48.972Z level=DEBUG source=file/file_plugin.go:258 msg="No changes required for config apply request" correlation_id=2f2ad4fd-d210-4be2-83cf-51069099dfb1 server_type=command' + + + + + + + + diff --git a/test/mock/grpc/mock_management_file_service.go b/test/mock/grpc/mock_management_file_service.go index 9b916cb76..82edfb67c 100644 --- a/test/mock/grpc/mock_management_file_service.go +++ b/test/mock/grpc/mock_management_file_service.go @@ -15,6 +15,7 @@ import ( "os" "path/filepath" "strconv" + // "time" "github.com/nginx/agent/v3/pkg/files" @@ -41,6 +42,7 @@ type FileService struct { instanceFiles map[string][]*v1.File // key is instanceID requestChan chan *v1.ManagementPlaneRequest configDirectory string + // tryCount int } func NewFileService(configDirectory string, requestChan chan *v1.ManagementPlaneRequest, @@ -51,6 +53,7 @@ func NewFileService(configDirectory string, requestChan chan *v1.ManagementPlane instanceFiles: make(map[string][]*v1.File), requestChan: requestChan, agentConfig: agentConfig, + // tryCount: 0, } } @@ -128,6 +131,13 @@ func (mgs *FileService) GetFile( return nil, status.Errorf(codes.Internal, "Failed to get file contents") } + // if mgs.tryCount == 0 { + // time.Sleep(2 * time.Minute) + // slog.ErrorContext(ctx, "File not found", "file_name", fileName) + // return nil, status.Errorf(codes.Internal, "Failed to get file contents") + // + // } + return &v1.GetFileResponse{ Contents: &v1.FileContents{ Contents: bytes, From 2f273c4861cb4e186d7df0fedf5a670fcf97915e Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Thu, 11 Sep 2025 15:16:52 +0100 Subject: [PATCH 06/13] clean up and check if manifest needs rollback --- internal/command/command_plugin.go | 5 +- internal/file/file_manager_service.go | 45 ++++--- internal/file/file_operator.go | 4 +- internal/file/file_plugin.go | 3 +- internal/file/file_service_operator.go | 3 +- .../fake_file_manager_service_interface.go | 22 ++-- internal/file/filefakes/fake_file_operator.go | 26 ++-- internal/grpc/grpc.go | 4 - test.txt | 119 ------------------ .../mock/grpc/mock_management_file_service.go | 10 -- 10 files changed, 63 insertions(+), 178 deletions(-) delete mode 100644 test.txt diff --git a/internal/command/command_plugin.go b/internal/command/command_plugin.go index bb52e65e6..616c48eb8 100644 --- a/internal/command/command_plugin.go +++ b/internal/command/command_plugin.go @@ -232,14 +232,14 @@ func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Me slog.DebugContext(ctx, "Command plugin received connection reset message") if newConnection, ok := msg.Data.(grpc.GrpcConnectionInterface); ok { - slog.InfoContext(ctx, "Resetting Subscribe") + slog.DebugContext(ctx, "Canceling Subscribe after connection reset") ctxWithMetadata := cp.config.NewContextWithLabels(ctx) cp.subscribeMutex.Lock() defer cp.subscribeMutex.Unlock() if cp.subscribeCancel != nil { cp.subscribeCancel() - slog.InfoContext(ctxWithMetadata, "Successfully Reset Subscribe") + slog.DebugContext(ctxWithMetadata, "Successfully canceled subscribe after connection reset") } connectionErr := cp.conn.Close(ctx) @@ -254,6 +254,7 @@ func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Me return } + slog.DebugContext(ctxWithMetadata, "Starting new subscribe after connection reset") subscribeCtx, cp.subscribeCancel = context.WithCancel(ctxWithMetadata) go cp.commandService.Subscribe(subscribeCtx) diff --git a/internal/file/file_manager_service.go b/internal/file/file_manager_service.go index e4f838956..bd784051d 100644 --- a/internal/file/file_manager_service.go +++ b/internal/file/file_manager_service.go @@ -52,7 +52,7 @@ type ( reader *bufio.Reader, chunkID uint32, ) (mpi.FileDataChunk_Content, error) - WriteManifestFile(updatedFiles map[string]*model.ManifestFile, + WriteManifestFile(ctx context.Context, updatedFiles map[string]*model.ManifestFile, manifestDir, manifestPath string) (writeError error) } @@ -68,7 +68,7 @@ type ( fileToUpdate *mpi.File, ) error SetIsConnected(isConnected bool) - UpdateClient(fileServiceClient mpi.FileServiceClient) + UpdateClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) } fileManagerServiceInterface interface { @@ -86,7 +86,7 @@ type ( ) (map[string]*model.FileCache, map[string][]byte, error) IsConnected() bool SetIsConnected(isConnected bool) - ResetClient(fileServiceClient mpi.FileServiceClient) + ResetClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) } ) @@ -103,6 +103,7 @@ type FileManagerService struct { currentFilesOnDisk map[string]*mpi.File // key is file path previousManifestFiles map[string]*model.ManifestFile manifestFilePath string + rollbackManifest bool filesMutex sync.RWMutex } @@ -118,12 +119,14 @@ func NewFileManagerService(fileServiceClient mpi.FileServiceClient, agentConfig currentFilesOnDisk: make(map[string]*mpi.File), previousManifestFiles: make(map[string]*model.ManifestFile), manifestFilePath: agentConfig.ManifestDir + "/manifest.json", + rollbackManifest: true, manifestLock: manifestLock, } } -func (fms *FileManagerService) ResetClient(fileServiceClient mpi.FileServiceClient) { - fms.fileServiceOperator.UpdateClient(fileServiceClient) +func (fms *FileManagerService) ResetClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) { + fms.fileServiceOperator.UpdateClient(ctx, fileServiceClient) + slog.DebugContext(ctx, "File manager service reset client successfully") } func (fms *FileManagerService) IsConnected() bool { @@ -167,6 +170,7 @@ func (fms *FileManagerService) ConfigApply(ctx context.Context, fileErr := fms.executeFileActions(ctx) if fileErr != nil { + fms.rollbackManifest = false return model.RollbackRequired, fileErr } fileOverviewFiles := files.ConvertToMapOfFiles(fileOverview.GetFiles()) @@ -185,6 +189,7 @@ func (fms *FileManagerService) ClearCache() { clear(fms.previousManifestFiles) } +//nolint:revive // cognitive-complexity of 13 max is 12, loop is needed cant be broken up func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string) error { slog.InfoContext(ctx, "Rolling back config for instance", "instance_id", instanceID) @@ -218,12 +223,13 @@ func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string) } } - slog.InfoContext(ctx, "Rolling back config for instance, writing manifest file", - "manifest_previous", fms.previousManifestFiles) - manifestFileErr := fms.fileOperator.WriteManifestFile(fms.previousManifestFiles, - fms.agentConfig.ManifestDir, fms.manifestFilePath) - if manifestFileErr != nil { - return manifestFileErr + if fms.rollbackManifest { + slog.DebugContext(ctx, "Rolling back manifest file", "manifest_previous", fms.previousManifestFiles) + manifestFileErr := fms.fileOperator.WriteManifestFile(ctx, fms.previousManifestFiles, + fms.agentConfig.ManifestDir, fms.manifestFilePath) + if manifestFileErr != nil { + return manifestFileErr + } } return nil @@ -382,7 +388,7 @@ func (fms *FileManagerService) UpdateCurrentFilesOnDisk( fms.currentFilesOnDisk[currentFile.GetFileMeta().GetName()] = currentFile } - err := fms.UpdateManifestFile(currentFiles, referenced) + err := fms.UpdateManifestFile(ctx, currentFiles, referenced) if err != nil { return fmt.Errorf("failed to update manifest file: %w", err) } @@ -393,17 +399,24 @@ func (fms *FileManagerService) UpdateCurrentFilesOnDisk( // seems to be a control flag, avoid control coupling // //nolint:revive // referenced is a required flag -func (fms *FileManagerService) UpdateManifestFile(currentFiles map[string]*mpi.File, referenced bool) (err error) { - slog.Debug("Updating manifest file", "current_files", currentFiles, "referenced", referenced) +func (fms *FileManagerService) UpdateManifestFile(ctx context.Context, + currentFiles map[string]*mpi.File, referenced bool, +) (err error) { + slog.DebugContext(ctx, "Updating manifest file", "current_files", currentFiles, "referenced", referenced) currentManifestFiles, _, readError := fms.manifestFile() + // When agent is first started the manifest is updated when an NGINX instance is found, but the manifest file + // will be empty leading to previousManifestFiles being empty. This was causing issues if the first config + // apply failed leading to the manifest file being rolled back to an empty file. + // If the currentManifestFiles is empty then we can assume the Agent has just started and this is the first + // write of the Manifest file, so set previousManifestFiles to be the currentFiles. if len(currentManifestFiles) == 0 { currentManifestFiles = fms.convertToManifestFileMap(currentFiles, referenced) } fms.previousManifestFiles = currentManifestFiles if readError != nil && !errors.Is(readError, os.ErrNotExist) { - slog.Debug("Error reading manifest file", "current_manifest_files", + slog.DebugContext(ctx, "Error reading manifest file", "current_manifest_files", currentManifestFiles, "updated_files", currentFiles, "referenced", referenced) return fmt.Errorf("unable to read manifest file: %w", readError) @@ -429,7 +442,7 @@ func (fms *FileManagerService) UpdateManifestFile(currentFiles map[string]*mpi.F updatedFiles = manifestFiles } - return fms.fileOperator.WriteManifestFile(updatedFiles, fms.agentConfig.ManifestDir, fms.manifestFilePath) + return fms.fileOperator.WriteManifestFile(ctx, updatedFiles, fms.agentConfig.ManifestDir, fms.manifestFilePath) } func (fms *FileManagerService) manifestFile() (map[string]*model.ManifestFile, map[string]*mpi.File, error) { diff --git a/internal/file/file_operator.go b/internal/file/file_operator.go index f65de514c..ef512cadb 100644 --- a/internal/file/file_operator.go +++ b/internal/file/file_operator.go @@ -149,10 +149,10 @@ func (fo *FileOperator) ReadChunk( return chunk, err } -func (fo *FileOperator) WriteManifestFile(updatedFiles map[string]*model.ManifestFile, manifestDir, +func (fo *FileOperator) WriteManifestFile(ctx context.Context, updatedFiles map[string]*model.ManifestFile, manifestDir, manifestPath string, ) (writeError error) { - slog.Info("Writing manifest file") + slog.DebugContext(ctx, "Writing manifest file", "updated_files", updatedFiles) manifestJSON, err := json.MarshalIndent(updatedFiles, "", " ") if err != nil { return fmt.Errorf("unable to marshal manifest file json: %w", err) diff --git a/internal/file/file_plugin.go b/internal/file/file_plugin.go index 6eedf6fff..39c0bdcd5 100644 --- a/internal/file/file_plugin.go +++ b/internal/file/file_plugin.go @@ -149,8 +149,7 @@ func (fp *FilePlugin) handleConnectionReset(ctx context.Context, msg *bus.Messag fp.conn = newConnection reconnect = fp.fileManagerService.IsConnected() - fp.fileManagerService.ResetClient(fp.conn.FileServiceClient()) - // fp.fileManagerService = NewFileManagerService(fp.conn.FileServiceClient(), fp.config, fp.manifestLock) + fp.fileManagerService.ResetClient(ctx, fp.conn.FileServiceClient()) fp.fileManagerService.SetIsConnected(reconnect) slog.DebugContext(ctx, "File manager service client reset successfully") diff --git a/internal/file/file_service_operator.go b/internal/file/file_service_operator.go index 16f229828..80d47ec0c 100644 --- a/internal/file/file_service_operator.go +++ b/internal/file/file_service_operator.go @@ -56,8 +56,9 @@ func NewFileServiceOperator(agentConfig *config.Config, fileServiceClient mpi.Fi } } -func (fso *FileServiceOperator) UpdateClient(fileServiceClient mpi.FileServiceClient) { +func (fso *FileServiceOperator) UpdateClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) { fso.fileServiceClient = fileServiceClient + slog.DebugContext(ctx, "File service operator updated client") } func (fso *FileServiceOperator) SetIsConnected(isConnected bool) { diff --git a/internal/file/filefakes/fake_file_manager_service_interface.go b/internal/file/filefakes/fake_file_manager_service_interface.go index 2c1b8ed5d..9d1943659 100644 --- a/internal/file/filefakes/fake_file_manager_service_interface.go +++ b/internal/file/filefakes/fake_file_manager_service_interface.go @@ -73,10 +73,11 @@ type FakeFileManagerServiceInterface struct { isConnectedReturnsOnCall map[int]struct { result1 bool } - ResetClientStub func(v1.FileServiceClient) + ResetClientStub func(context.Context, v1.FileServiceClient) resetClientMutex sync.RWMutex resetClientArgsForCall []struct { - arg1 v1.FileServiceClient + arg1 context.Context + arg2 v1.FileServiceClient } RollbackStub func(context.Context, string) error rollbackMutex sync.RWMutex @@ -418,16 +419,17 @@ func (fake *FakeFileManagerServiceInterface) IsConnectedReturnsOnCall(i int, res }{result1} } -func (fake *FakeFileManagerServiceInterface) ResetClient(arg1 v1.FileServiceClient) { +func (fake *FakeFileManagerServiceInterface) ResetClient(arg1 context.Context, arg2 v1.FileServiceClient) { fake.resetClientMutex.Lock() fake.resetClientArgsForCall = append(fake.resetClientArgsForCall, struct { - arg1 v1.FileServiceClient - }{arg1}) + arg1 context.Context + arg2 v1.FileServiceClient + }{arg1, arg2}) stub := fake.ResetClientStub - fake.recordInvocation("ResetClient", []interface{}{arg1}) + fake.recordInvocation("ResetClient", []interface{}{arg1, arg2}) fake.resetClientMutex.Unlock() if stub != nil { - fake.ResetClientStub(arg1) + fake.ResetClientStub(arg1, arg2) } } @@ -437,17 +439,17 @@ func (fake *FakeFileManagerServiceInterface) ResetClientCallCount() int { return len(fake.resetClientArgsForCall) } -func (fake *FakeFileManagerServiceInterface) ResetClientCalls(stub func(v1.FileServiceClient)) { +func (fake *FakeFileManagerServiceInterface) ResetClientCalls(stub func(context.Context, v1.FileServiceClient)) { fake.resetClientMutex.Lock() defer fake.resetClientMutex.Unlock() fake.ResetClientStub = stub } -func (fake *FakeFileManagerServiceInterface) ResetClientArgsForCall(i int) v1.FileServiceClient { +func (fake *FakeFileManagerServiceInterface) ResetClientArgsForCall(i int) (context.Context, v1.FileServiceClient) { fake.resetClientMutex.RLock() defer fake.resetClientMutex.RUnlock() argsForCall := fake.resetClientArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeFileManagerServiceInterface) Rollback(arg1 context.Context, arg2 string) error { diff --git a/internal/file/filefakes/fake_file_operator.go b/internal/file/filefakes/fake_file_operator.go index 77445acdc..3b2b2ee6c 100644 --- a/internal/file/filefakes/fake_file_operator.go +++ b/internal/file/filefakes/fake_file_operator.go @@ -69,12 +69,13 @@ type FakeFileOperator struct { writeChunkedFileReturnsOnCall map[int]struct { result1 error } - WriteManifestFileStub func(map[string]*model.ManifestFile, string, string) error + WriteManifestFileStub func(context.Context, map[string]*model.ManifestFile, string, string) error writeManifestFileMutex sync.RWMutex writeManifestFileArgsForCall []struct { - arg1 map[string]*model.ManifestFile - arg2 string + arg1 context.Context + arg2 map[string]*model.ManifestFile arg3 string + arg4 string } writeManifestFileReturns struct { result1 error @@ -348,20 +349,21 @@ func (fake *FakeFileOperator) WriteChunkedFileReturnsOnCall(i int, result1 error }{result1} } -func (fake *FakeFileOperator) WriteManifestFile(arg1 map[string]*model.ManifestFile, arg2 string, arg3 string) error { +func (fake *FakeFileOperator) WriteManifestFile(arg1 context.Context, arg2 map[string]*model.ManifestFile, arg3 string, arg4 string) error { fake.writeManifestFileMutex.Lock() ret, specificReturn := fake.writeManifestFileReturnsOnCall[len(fake.writeManifestFileArgsForCall)] fake.writeManifestFileArgsForCall = append(fake.writeManifestFileArgsForCall, struct { - arg1 map[string]*model.ManifestFile - arg2 string + arg1 context.Context + arg2 map[string]*model.ManifestFile arg3 string - }{arg1, arg2, arg3}) + arg4 string + }{arg1, arg2, arg3, arg4}) stub := fake.WriteManifestFileStub fakeReturns := fake.writeManifestFileReturns - fake.recordInvocation("WriteManifestFile", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("WriteManifestFile", []interface{}{arg1, arg2, arg3, arg4}) fake.writeManifestFileMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1 @@ -375,17 +377,17 @@ func (fake *FakeFileOperator) WriteManifestFileCallCount() int { return len(fake.writeManifestFileArgsForCall) } -func (fake *FakeFileOperator) WriteManifestFileCalls(stub func(map[string]*model.ManifestFile, string, string) error) { +func (fake *FakeFileOperator) WriteManifestFileCalls(stub func(context.Context, map[string]*model.ManifestFile, string, string) error) { fake.writeManifestFileMutex.Lock() defer fake.writeManifestFileMutex.Unlock() fake.WriteManifestFileStub = stub } -func (fake *FakeFileOperator) WriteManifestFileArgsForCall(i int) (map[string]*model.ManifestFile, string, string) { +func (fake *FakeFileOperator) WriteManifestFileArgsForCall(i int) (context.Context, map[string]*model.ManifestFile, string, string) { fake.writeManifestFileMutex.RLock() defer fake.writeManifestFileMutex.RUnlock() argsForCall := fake.writeManifestFileArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeFileOperator) WriteManifestFileReturns(result1 error) { diff --git a/internal/grpc/grpc.go b/internal/grpc/grpc.go index a7ed75c67..4fb036187 100644 --- a/internal/grpc/grpc.go +++ b/internal/grpc/grpc.go @@ -231,10 +231,6 @@ func DialOptions(agentConfig *config.Config, commandConfig *config.Command, reso opts = addPerRPCCredentials(commandConfig, resourceID, opts) } - // opts = append(opts, - // grpc.WithTransportCredentials(defaultCredentials), - // ) - return opts } diff --git a/test.txt b/test.txt deleted file mode 100644 index bb0e3e628..000000000 --- a/test.txt +++ /dev/null @@ -1,119 +0,0 @@ - -time=2025-09-09T15:13:02.846Z level=DEBUG source=command/command_plugin.go:274 msg="Received management plane request" request="message_meta:{message_id:\\"3a11dc25-267c-41d8-87f9-7b7cdd2863bc\\" correlation_id:\\"3ac7c2ef-a756-4820-b407-e94f64531869\\" timestamp:{seconds:1757430782 nanos:842281487}} -config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} -files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"gzh1NUznNQjgHWysBFcM0U4qepgzAtGoXDkYREx47IQ=\\" permissions:\\"0644\\" size:5419}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} -files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} -files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} -config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"nF8he2FHbzLuMMvHjvek3OiM8dQmNmOu6j2U8O1ILus=\\"}}}" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' - - -'time=2025-09-09T15:13:03.319Z level=INFO source=command/command_plugin.go:221 msg="Sending data plane response message" message="Config apply successful" status=COMMAND_STATUS_OK correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' - - -'time=2025-09-09T15:13:03.319Z level=DEBUG source=command/command_service.go:169 msg="Sending data plane response" response="message_meta:{message_id:\\"7b105f0f-8d8f-11f0-8e45-f69b220a4435\\" correlation_id:\\"3ac7c2ef-a756-4820-b407-e94f64531869\\" timestamp:{seconds:1757430783 nanos:299561456}} -command_response:{status:COMMAND_STATUS_OK message:\\"Config apply successful\\"} instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\"" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' - - --------------- - -'time=2025-09-09T15:13:03.321Z level=DEBUG source=command/command_service.go:364 msg="Removed config apply requests from queue" queue=[] correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 --- Think this is just a debug that should not be there - -token reset - - -'time=2025-09-09T15:13:03.324Z level=DEBUG source=file/file_plugin.go:354 msg="File plugin received nginx config update message" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' -time=2025-09-09T15:13:03.395Z level=DEBUG source=file/file_service_operator.go:174 msg="UpdateOverview response" response="" correlation_id=7b149162-8d8f-11f0-8e45-f69b220a4435 server_type=command' -'time=2025-09-09T15:13:03.396Z level=DEBUG source=file/file_service_operator.go:177 msg="UpdateOverview response is empty" correlation_id=3ac7c2ef-a756-4820-b407-e94f64531869 server_type=command' - - - -------- - - - - -time=2025-09-09T15:13:03.397Z level=DEBUG source=command/command_plugin.go:274 msg="Received management plane request" request="message_meta:{message_id:\\"fc8d4120-1a11-4780-abb6-f9c94466a49d\\" correlation_id:\\"e0ca9bc8-9bdd-42b5-be15-2a64f543c555\\" timestamp:{seconds:1757430783 nanos:336746495}} -config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} -files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} -files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} -files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} -config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}" correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' - - -'time=2025-09-09T15:13:03.402Z level=DEBUG source=file/file_service_operator.go:70 msg="Getting file" file=/etc/nginx/conf.d/http.conf correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 - - - -'time=2025-09-09T15:13:03.406Z level=DEBUG source=watcher/watcher_plugin.go:311 msg="Received credential update event for command server" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' -'time=2025-09-09T15:13:03.407Z level=DEBUG source=file/file_plugin.go:142 msg="File plugin received connection reset message" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' -'time=2025-09-09T15:13:03.407Z level=INFO source=grpc/grpc.go:129 msg="Closing grpc connection" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' -'time=2025-09-09T15:13:03.409Z level=ERROR source=command/command_service.go:468 msg="Failed toreceive message from subscribe stream" error="rpc error: code = Canceled desc = context canceled" correlation_id=4b012305-8d8e-11f0-8e45-f69b220a4435 server_type=command' -'time=2025-09-09T15:13:03.409Z level=WARN source=command/command_service.go:201 msg="Failed to receive messages from subscribe stream" error="context canceled" correlation_id=4b012305-8d8e-11f0-8e45-f69b220a4435 server_type=command' -'time=2025-09-09T15:13:03.413Z level=DEBUG source=file/file_plugin.go:155 msg="File manager service client reset successfully" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' - - -'time=2025-09-09T15:13:03.413Z level=DEBUG source=command/command_service.go:237 msg="Sending create connection request" request="message_meta:{message_id:\\"7b21d4ef-8d8f-11f0-8e45-f69b220a4435\\" correlation_id:\\"7afcd968-8d8f-11f0-8e45-f69b220a4435\\" timestamp:{seconds:1757430783 nanos:413990004}}" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' - -'time=2025-09-09T15:13:03.820Z level=INFO source=command/command_service.go:246 msg="Connection created" response=response:{status:COMMAND_STATUS_OK} correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' - -'time=2025-09-09T15:13:03.820Z level=INFO source=command/command_service.go:247 msg="Agent connected" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' - - -'time=2025-09-09T15:13:04.907Z level=DEBUG source=command/command_service.go:482 msg="Config apply request is already in progress, queuing new config apply request" request="message_meta:{message_id:\\"1d1f86d3-195d-4e17-8250-2e0f5511ffd3\\" correlation_id:\\"2f2ad4fd-d210-4be2-83cf-51069099dfb1\\" timestamp:{seconds:1757430784 nanos:844163401}} -config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} -files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} -files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} -files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} -config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}" correlation_id=7afcd968-8d8f-11f0-8e45-f69b220a4435 server_type=command' - - -'time=2025-09-09T15:13:48.969Z level=ERROR source=file/file_plugin.go:296 msg="Failed to apply config changes, rolling back" instance_id=e8d1bda6-397e-3b98-a179-e500ff99fbc7 error="error getting file data for name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" -permissions:\\"0644\\" size:5390: rpc error: code = Canceled desc = grpc: the client connection is closing" correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' - - 'time=2025-09-09T15:13:48.970Z level=DEBUG source=command/command_service.go:169 msg="Sending data plane response" response="message_meta:{message_id:\\"96490f35-8d8f-11f0-8e45-f69b220a4435\\" correlation_id:\\"e0ca9bc8-9bdd-42b5-be15-2a64f543c555\\" - timestamp:{seconds:1757430828 nanos:969555159}} command_response:{status:COMMAND_STATUS_ERROR message:\\"Config apply failed, rolling back config\\" - error:\\"error getting file data for name:\\\\\\"/etc/nginx/conf.d/http.conf\\\\\\" hash:\\\\\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\\\\\" permissions:\\\\\\"0644\\\\\\" size:5390: rpc error: code = Canceled desc = grpc: the client connection is closing\\"} instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\"" - correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' - - - - -'time=2025-09-09T15:13:48.970Z level=DEBUG source=command/command_service.go:364 msg="Removed config apply requests from queue" queue="[message_meta:{message_id:\\"1d1f86d3-195d-4e17-8250-2e0f5511ffd3\\" correlation_id:\\"2f2ad4fd-d210-4be2-83cf-51069099dfb1\\" timestamp:{seconds:1757430784 nanos:844163401}} -config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} -files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} -files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} -files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} -config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}]" correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' - -'time=2025-09-09T15:13:48.971Z level=INFO source=command/command_plugin.go:221 msg="Sending data plane response message" message="Config apply failed, rollback successful" status=COMMAND_STATUS_FAILURE correlation_id=e0ca9bc8-9bdd-42b5-be15-2a64f543c555 server_type=command' - - - - - -time=2025-09-09T15:13:48.971Z level=DEBUG source=command/command_plugin.go:274 msg="Received management plane request" request="message_meta:{message_id:\\"1d1f86d3-195d-4e17-8250-2e0f5511ffd3\\" correlation_id:\\"2f2ad4fd-d210-4be2-83cf-51069099dfb1\\" timestamp:{seconds:1757430784 nanos:844163401}} -config_apply_request:{overview:{files:{file_meta:{name:\\"/etc/nginx/secrets/ssl_keypair_longevity_cafe-secret.pem\\" hash:\\"S9cK2YCKK9Aetx9K95IrlMts6xigJiFM347VdcftPPc=\\" permissions:\\"0640\\" size:2702}} -files:{file_meta:{name:\\"/etc/nginx/main-includes/main.conf\\" hash:\\"syW4hE1LvlfU1bY4bxl5Il13GHaadPDH1S8UQ6leZMo=\\" permissions:\\"0644\\" size:26}} files:{file_meta:{name:\\"/etc/nginx/events-includes/events.conf\\" hash:\\"tCt8WOA6/Fqu/Vwuvwh3Snxw/fad32qmTvmsu/RATr0=\\" permissions:\\"0644\\" size:26}} -files:{file_meta:{name:\\"/etc/nginx/conf.d/http.conf\\" hash:\\"mHgswNnClBq4riZh56Sjx9arLNW8FRpUB1hqJUxVJEA=\\" permissions:\\"0644\\" size:5390}} files:{file_meta:{name:\\"/etc/nginx/conf.d/matches.json\\" hash:\\"RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=\\" permissions:\\"0644\\" size:2}} -files:{file_meta:{name:\\"/etc/nginx/stream-conf.d/stream.conf\\" hash:\\"soWD6OqhVahUSsVG+Yov6w7tTyVmbLkTWMVPNSoByoY=\\" permissions:\\"0644\\" size:93}} files:{file_meta:{name:\\"/etc/nginx/nginx.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/mime.types\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/etc/nginx/grpc-error-locations.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/etc/nginx/grpc-error-pages.conf\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/50x.html\\" permissions:\\"0644\\"} unmanaged:true} -files:{file_meta:{name:\\"/usr/share/nginx/html/dashboard.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/index.html\\" permissions:\\"0644\\"} unmanaged:true} files:{file_meta:{name:\\"/usr/share/nginx/html/nginx-modules-reference.pdf\\" permissions:\\"0644\\"} unmanaged:true} -config_version:{instance_id:\\"e8d1bda6-397e-3b98-a179-e500ff99fbc7\\" version:\\"3fjckujQN+QgdDKHpEcs6olpnhLige9UDKvfuQtpgrk=\\"}}}" correlation_id=2f2ad4fd-d210-4be2-83cf-51069099dfb1 server_type=command - -'time=2025-09-09T15:13:48.972Z level=DEBUG source=file/file_plugin.go:258 msg="No changes required for config apply request" correlation_id=2f2ad4fd-d210-4be2-83cf-51069099dfb1 server_type=command' - - - - - - - - diff --git a/test/mock/grpc/mock_management_file_service.go b/test/mock/grpc/mock_management_file_service.go index 00511d2f6..a865777c7 100644 --- a/test/mock/grpc/mock_management_file_service.go +++ b/test/mock/grpc/mock_management_file_service.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "strconv" - // "time" "github.com/nginx/agent/v3/pkg/files" @@ -43,7 +42,6 @@ type FileService struct { instanceID string requestChan chan *v1.ManagementPlaneRequest configDirectory string - // tryCount int } func NewFileService(configDirectory string, requestChan chan *v1.ManagementPlaneRequest, @@ -55,7 +53,6 @@ func NewFileService(configDirectory string, requestChan chan *v1.ManagementPlane instanceID: "", requestChan: requestChan, agentConfig: agentConfig, - // tryCount: 0, } } @@ -135,13 +132,6 @@ func (mgs *FileService) GetFile( return nil, status.Errorf(codes.Internal, "Failed to get file contents") } - // if mgs.tryCount == 0 { - // time.Sleep(2 * time.Minute) - // slog.ErrorContext(ctx, "File not found", "file_name", fileName) - // return nil, status.Errorf(codes.Internal, "Failed to get file contents") - // - // } - return &v1.GetFileResponse{ Contents: &v1.FileContents{ Contents: bytes, From 1534e68951c1d6ea3cb1d38968e232af7b64a829 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Thu, 11 Sep 2025 17:01:42 +0100 Subject: [PATCH 07/13] reset bool --- internal/file/file_manager_service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/file/file_manager_service.go b/internal/file/file_manager_service.go index bd784051d..3c7b51251 100644 --- a/internal/file/file_manager_service.go +++ b/internal/file/file_manager_service.go @@ -140,6 +140,7 @@ func (fms *FileManagerService) SetIsConnected(isConnected bool) { func (fms *FileManagerService) ConfigApply(ctx context.Context, configApplyRequest *mpi.ConfigApplyRequest, ) (status model.WriteStatus, err error) { + fms.rollbackManifest = true fileOverview := configApplyRequest.GetOverview() if fileOverview == nil { From 0568cce6f597b8cc821fbb91210ae9cce9b74908 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Fri, 12 Sep 2025 13:33:31 +0100 Subject: [PATCH 08/13] add metric attributes --- .../nginxplusreceiver/documentation.md | 7 + .../internal/metadata/generated_metrics.go | 9 +- .../metadata/generated_metrics_test.go | 8 +- .../collector/nginxplusreceiver/metadata.yaml | 3 + .../collector/nginxplusreceiver/scraper.go | 37 +- .../nginxplusreceiver/scraper_test.go | 8 + .../nginxplusreceiver/testdata/expected.yaml | 25 + .../dashboards/nginx-plus-dashboard.json | 435 ++++++++++-------- 8 files changed, 341 insertions(+), 191 deletions(-) diff --git a/internal/collector/nginxplusreceiver/documentation.md b/internal/collector/nginxplusreceiver/documentation.md index 67fc5abb3..3d8a93887 100644 --- a/internal/collector/nginxplusreceiver/documentation.md +++ b/internal/collector/nginxplusreceiver/documentation.md @@ -144,6 +144,13 @@ The total number of client requests received, since the last collection interval | ---- | ----------- | ---------- | | requests | Gauge | Int | +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| nginx.zone.name | The name of the shared memory zone. | Any Str | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | + ### nginx.http.request.discarded The total number of requests completed without sending a response. diff --git a/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics.go b/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics.go index e812fe359..83c262af7 100644 --- a/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics.go +++ b/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics.go @@ -1173,9 +1173,10 @@ func (m *metricNginxHTTPRequestCount) init() { m.data.SetDescription("The total number of client requests received, since the last collection interval.") m.data.SetUnit("requests") m.data.SetEmptyGauge() + m.data.Gauge().DataPoints().EnsureCapacity(m.capacity) } -func (m *metricNginxHTTPRequestCount) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) { +func (m *metricNginxHTTPRequestCount) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, nginxZoneNameAttributeValue string, nginxZoneTypeAttributeValue string) { if !m.config.Enabled { return } @@ -1183,6 +1184,8 @@ func (m *metricNginxHTTPRequestCount) recordDataPoint(start pcommon.Timestamp, t dp.SetStartTimestamp(start) dp.SetTimestamp(ts) dp.SetIntValue(val) + dp.Attributes().PutStr("nginx.zone.name", nginxZoneNameAttributeValue) + dp.Attributes().PutStr("nginx.zone.type", nginxZoneTypeAttributeValue) } // updateCapacity saves max length of data point slices that will be used for the slice capacity. @@ -4225,8 +4228,8 @@ func (mb *MetricsBuilder) RecordNginxHTTPLimitReqRequestsDataPoint(ts pcommon.Ti } // RecordNginxHTTPRequestCountDataPoint adds a data point to nginx.http.request.count metric. -func (mb *MetricsBuilder) RecordNginxHTTPRequestCountDataPoint(ts pcommon.Timestamp, val int64) { - mb.metricNginxHTTPRequestCount.recordDataPoint(mb.startTime, ts, val) +func (mb *MetricsBuilder) RecordNginxHTTPRequestCountDataPoint(ts pcommon.Timestamp, val int64, nginxZoneNameAttributeValue string, nginxZoneTypeAttributeValue AttributeNginxZoneType) { + mb.metricNginxHTTPRequestCount.recordDataPoint(mb.startTime, ts, val, nginxZoneNameAttributeValue, nginxZoneTypeAttributeValue.String()) } // RecordNginxHTTPRequestDiscardedDataPoint adds a data point to nginx.http.request.discarded metric. diff --git a/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics_test.go b/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics_test.go index a36035d80..16aacf811 100644 --- a/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics_test.go +++ b/internal/collector/nginxplusreceiver/internal/metadata/generated_metrics_test.go @@ -106,7 +106,7 @@ func TestMetricsBuilder(t *testing.T) { defaultMetricsCount++ allMetricsCount++ - mb.RecordNginxHTTPRequestCountDataPoint(ts, 1) + mb.RecordNginxHTTPRequestCountDataPoint(ts, 1, "nginx.zone.name-val", AttributeNginxZoneTypeSERVER) defaultMetricsCount++ allMetricsCount++ @@ -497,6 +497,12 @@ func TestMetricsBuilder(t *testing.T) { assert.Equal(t, ts, dp.Timestamp()) assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) assert.Equal(t, int64(1), dp.IntValue()) + attrVal, ok := dp.Attributes().Get("nginx.zone.name") + assert.True(t, ok) + assert.Equal(t, "nginx.zone.name-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("nginx.zone.type") + assert.True(t, ok) + assert.Equal(t, "SERVER", attrVal.Str()) case "nginx.http.request.discarded": assert.False(t, validatedMetrics["nginx.http.request.discarded"], "Found a duplicate in the metrics slice: nginx.http.request.discarded") validatedMetrics["nginx.http.request.discarded"] = true diff --git a/internal/collector/nginxplusreceiver/metadata.yaml b/internal/collector/nginxplusreceiver/metadata.yaml index 9b3fb2597..9e8d93724 100644 --- a/internal/collector/nginxplusreceiver/metadata.yaml +++ b/internal/collector/nginxplusreceiver/metadata.yaml @@ -173,6 +173,9 @@ metrics: gauge: value_type: int unit: "requests" + attributes: + - nginx.zone.name + - nginx.zone.type nginx.cache.bytes_read: enabled: true description: The total number of bytes read from the cache or proxied server. diff --git a/internal/collector/nginxplusreceiver/scraper.go b/internal/collector/nginxplusreceiver/scraper.go index bbf3285fe..e677e2451 100644 --- a/internal/collector/nginxplusreceiver/scraper.go +++ b/internal/collector/nginxplusreceiver/scraper.go @@ -42,6 +42,8 @@ const ( type NginxPlusScraper struct { previousServerZoneResponses map[string]ResponseStatuses previousLocationZoneResponses map[string]ResponseStatuses + previousServerZoneRequests map[string]int64 + previousLocationZoneRequests map[string]int64 plusClient *plusapi.NginxClient cfg *Config mb *metadata.MetricsBuilder @@ -137,6 +139,8 @@ func (nps *NginxPlusScraper) Scrape(ctx context.Context) (pmetric.Metrics, error nps.previousHTTPRequestsTotal = stats.HTTPRequests.Total nps.createPreviousServerZoneResponses(stats) nps.createPreviousLocationZoneResponses(stats) + nps.createPreviousServerZoneRequests(stats) + nps.createPreviousLocationZoneRequests(stats) }) stats, err := nps.plusClient.GetStats(ctx) @@ -175,6 +179,22 @@ func (nps *NginxPlusScraper) createPreviousLocationZoneResponses(stats *plusapi. nps.previousLocationZoneResponses = previousLocationZoneResponses } +func (nps *NginxPlusScraper) createPreviousServerZoneRequests(stats *plusapi.Stats) { + previousServerZoneRequests := make(map[string]int64) + for szName, sz := range stats.ServerZones { + previousServerZoneRequests[szName] = int64(sz.Requests) + } + nps.previousServerZoneRequests = previousServerZoneRequests +} + +func (nps *NginxPlusScraper) createPreviousLocationZoneRequests(stats *plusapi.Stats) { + previousLocationZoneRequests := make(map[string]int64) + for lzName, lz := range stats.LocationZones { + previousLocationZoneRequests[lzName] = lz.Requests + } + nps.previousLocationZoneRequests = previousLocationZoneRequests +} + func (nps *NginxPlusScraper) createPreviousServerZoneResponses(stats *plusapi.Stats) { previousServerZoneResponses := make(map[string]ResponseStatuses) for szName, sz := range stats.ServerZones { @@ -224,7 +244,7 @@ func (nps *NginxPlusScraper) recordMetrics(stats *plusapi.Stats) { nps.mb.RecordNginxHTTPRequestsDataPoint(now, int64(stats.HTTPRequests.Total), "", 0) requestsDiff := int64(stats.HTTPRequests.Total) - int64(nps.previousHTTPRequestsTotal) - nps.mb.RecordNginxHTTPRequestCountDataPoint(now, requestsDiff) + nps.mb.RecordNginxHTTPRequestCountDataPoint(now, requestsDiff, "", 0) nps.previousHTTPRequestsTotal = stats.HTTPRequests.Total nps.recordCacheMetrics(stats, now) @@ -866,6 +886,13 @@ func (nps *NginxPlusScraper) recordServerZoneMetrics(stats *plusapi.Stats, now p nps.mb.RecordNginxHTTPRequestsDataPoint(now, int64(sz.Requests), szName, metadata.AttributeNginxZoneTypeSERVER) + nps.mb.RecordNginxHTTPRequestCountDataPoint(now, + int64(sz.Requests)-nps.previousServerZoneRequests[szName], + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + nps.previousServerZoneRequests[szName] = int64(sz.Requests) + nps.recordServerZoneHTTPMetrics(sz, szName, now) nps.mb.RecordNginxHTTPRequestDiscardedDataPoint(now, int64(sz.Discarded), @@ -975,6 +1002,14 @@ func (nps *NginxPlusScraper) recordLocationZoneMetrics(stats *plusapi.Stats, now metadata.AttributeNginxZoneTypeLOCATION, ) + nps.mb.RecordNginxHTTPRequestCountDataPoint(now, + lz.Requests-nps.previousLocationZoneRequests[lzName], + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + nps.previousLocationZoneRequests[lzName] = lz.Requests + nps.recordLocationZoneHTTPMetrics(lz, lzName, now) nps.mb.RecordNginxHTTPRequestDiscardedDataPoint(now, lz.Discarded, diff --git a/internal/collector/nginxplusreceiver/scraper_test.go b/internal/collector/nginxplusreceiver/scraper_test.go index 26aa45a69..954a0ad8e 100644 --- a/internal/collector/nginxplusreceiver/scraper_test.go +++ b/internal/collector/nginxplusreceiver/scraper_test.go @@ -58,6 +58,14 @@ func TestScraper(t *testing.T) { }, } + scraper.previousLocationZoneRequests = map[string]int64{ + "location_test": 30, // 5 + } + + scraper.previousServerZoneRequests = map[string]int64{ + "test": 29, // 3 + } + scraper.previousHTTPRequestsTotal = 3 actualMetrics, err := scraper.Scrape(context.Background()) diff --git a/internal/collector/nginxplusreceiver/testdata/expected.yaml b/internal/collector/nginxplusreceiver/testdata/expected.yaml index 724ed78d5..14e87717d 100644 --- a/internal/collector/nginxplusreceiver/testdata/expected.yaml +++ b/internal/collector/nginxplusreceiver/testdata/expected.yaml @@ -297,6 +297,31 @@ resourceMetrics: dataPoints: - asInt: "44" timeUnixNano: "1000000" + attributes: + - key: nginx.zone.name + value: + stringValue: "" + - key: nginx.zone.type + value: + stringValue: "" + - asInt: "3" + attributes: + - key: nginx.zone.name + value: + stringValue: test + - key: nginx.zone.type + value: + stringValue: SERVER + timeUnixNano: "1000000" + - asInt: "4" + attributes: + - key: nginx.zone.name + value: + stringValue: location_test + - key: nginx.zone.type + value: + stringValue: LOCATION + timeUnixNano: "1000000" isMonotonic: true unit: requests - description: The total number of bytes read from the cache or proxied server. diff --git a/test/mock/collector/grafana/provisioning/dashboards/nginx-plus-dashboard.json b/test/mock/collector/grafana/provisioning/dashboards/nginx-plus-dashboard.json index 193ea7468..329779b4e 100644 --- a/test/mock/collector/grafana/provisioning/dashboards/nginx-plus-dashboard.json +++ b/test/mock/collector/grafana/provisioning/dashboards/nginx-plus-dashboard.json @@ -85,7 +85,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -116,7 +116,7 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "datasource": { @@ -151,6 +151,7 @@ "mode": "palette-classic" }, "custom": { + "axisPlacement": "auto", "fillOpacity": 70, "hideFrom": { "legend": false, @@ -165,7 +166,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 } ] } @@ -224,7 +225,7 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "datasource": { @@ -266,7 +267,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -332,7 +333,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "datasource": { @@ -385,7 +386,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -418,7 +419,7 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, @@ -490,7 +491,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 } ] } @@ -517,7 +518,7 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "datasource": { @@ -557,7 +558,7 @@ "steps": [ { "color": "semi-dark-red", - "value": null + "value": 0 }, { "color": "dark-green", @@ -616,7 +617,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "datasource": { @@ -652,7 +653,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -694,7 +695,7 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, @@ -744,7 +745,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -786,7 +787,7 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, @@ -821,39 +822,64 @@ }, { "datasource": { + "default": true, "type": "prometheus", "uid": "otel-prometheus-scraper" }, - "description": "The total number of requests completed without sending a response.", + "description": "The total number of client requests received, since last collection interval", "fieldConfig": { "defaults": { "color": { - "mode": "continuous-BlPu" + "mode": "thresholds" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "semi-dark-red", + "value": 0 }, { - "color": "red", - "value": 80 + "color": "dark-green", + "value": 1 } ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": " " + }, + "properties": [ + { + "id": "displayName", + "value": "Total" + } + ] + }, + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [] + } + ] }, "gridPos": { "h": 8, - "w": 8, - "x": 16, + "w": 6, + "x": 18, "y": 20 }, - "id": 14, + "id": 66, "options": { "colorMode": "value", "graphMode": "area", @@ -871,19 +897,8 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ - { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "nginx_http_request_discarded{instance_type=\"nginxplus\", nginx_zone_type=\"SERVER\", nginx_zone_name=~\"$nginxServerZoneName\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "legendFormat": "{{nginx_zone_type}} {{nginx_zone_name}}", - "range": true, - "refId": "A", - "useBackend": false - }, { "datasource": { "type": "prometheus", @@ -891,17 +906,18 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "nginx_http_request_discarded{instance_type=\"nginxplus\", nginx_zone_type=\"LOCATION\", nginx_zone_name=~\"$nginxLocationZoneName\"}", + "expr": "nginx_http_request_count{instance_type=\"nginxplus\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, + "instant": false, "legendFormat": "{{nginx_zone_type}} {{nginx_zone_name}}", "range": true, - "refId": "B", + "refId": "A", "useBackend": false } ], - "title": "HTTP Requests Discarded", + "title": "HTTP Request Count", "type": "stat" }, { @@ -954,7 +970,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -985,7 +1001,7 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, @@ -1007,7 +1023,92 @@ "type": "prometheus", "uid": "otel-prometheus-scraper" }, - "description": "The total number of responses for ServerZone, grouped by status code range.", + "description": "The total number of requests completed without sending a response.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-BlPu" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 28 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "nginx_http_request_discarded{instance_type=\"nginxplus\", nginx_zone_type=\"SERVER\", nginx_zone_name=~\"$nginxServerZoneName\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "{{nginx_zone_type}} {{nginx_zone_name}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "otel-prometheus-scraper" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "nginx_http_request_discarded{instance_type=\"nginxplus\", nginx_zone_type=\"LOCATION\", nginx_zone_name=~\"$nginxLocationZoneName\"}", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "legendFormat": "{{nginx_zone_type}} {{nginx_zone_name}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "HTTP Requests Discarded", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "otel-prometheus-scraper" + }, + "description": "The total number of HTTP responses sent to clients since the last collection interval, grouped by status code range.", "fieldConfig": { "defaults": { "color": { @@ -1052,7 +1153,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -1066,10 +1167,10 @@ "gridPos": { "h": 7, "w": 12, - "x": 12, - "y": 28 + "x": 0, + "y": 35 }, - "id": 7, + "id": 8, "options": { "legend": { "calcs": [], @@ -1083,12 +1184,12 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, "editorMode": "builder", - "expr": "nginx_http_response_status{nginx_zone_type=\"SERVER\", nginx_zone_name=~\"$nginxServerZoneName\"}", + "expr": "nginx_http_response_count{nginx_zone_type=\"LOCATION\", nginx_zone_name=~\"$nginxLocationZoneName\"}", "fullMetaSearch": false, "includeNullMetadata": true, "legendFormat": "Status Range: {{nginx_status_range}} {{nginx_zone_name}}", @@ -1097,7 +1198,7 @@ "useBackend": false } ], - "title": "HTTP Response Status - ServerZone", + "title": "HTTP Response Count - LocationZone", "type": "timeseries" }, { @@ -1105,7 +1206,7 @@ "type": "prometheus", "uid": "otel-prometheus-scraper" }, - "description": "The total number of HTTP responses sent to clients since the last collection interval, grouped by status code range.", + "description": "The total number of responses for ServerZone, grouped by status code range.", "fieldConfig": { "defaults": { "color": { @@ -1150,7 +1251,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -1164,10 +1265,10 @@ "gridPos": { "h": 7, "w": 12, - "x": 0, - "y": 35 + "x": 12, + "y": 36 }, - "id": 8, + "id": 7, "options": { "legend": { "calcs": [], @@ -1181,12 +1282,12 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, "editorMode": "builder", - "expr": "nginx_http_response_count{nginx_zone_type=\"LOCATION\", nginx_zone_name=~\"$nginxLocationZoneName\"}", + "expr": "nginx_http_response_status{nginx_zone_type=\"SERVER\", nginx_zone_name=~\"$nginxServerZoneName\"}", "fullMetaSearch": false, "includeNullMetadata": true, "legendFormat": "Status Range: {{nginx_status_range}} {{nginx_zone_name}}", @@ -1195,7 +1296,7 @@ "useBackend": false } ], - "title": "HTTP Response Count - LocationZone", + "title": "HTTP Response Status - ServerZone", "type": "timeseries" }, { @@ -1248,7 +1349,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 } ] } @@ -1259,7 +1360,7 @@ "h": 7, "w": 12, "x": 12, - "y": 35 + "y": 43 }, "id": 9, "options": { @@ -1275,7 +1376,7 @@ "sort": "none" } }, - "pluginVersion": "11.5.2", + "pluginVersion": "12.1.1", "targets": [ { "disableTextWrap": false, @@ -1298,7 +1399,7 @@ "h": 1, "w": 24, "x": 0, - "y": 42 + "y": 50 }, "id": 11, "panels": [], @@ -1321,8 +1422,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1333,7 +1433,7 @@ "h": 6, "w": 5, "x": 0, - "y": 43 + "y": 51 }, "id": 18, "options": { @@ -1391,8 +1491,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "orange", @@ -1407,7 +1506,7 @@ "h": 6, "w": 7, "x": 5, - "y": 43 + "y": 51 }, "id": 32, "options": { @@ -1459,8 +1558,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1561,7 +1659,7 @@ "h": 18, "w": 12, "x": 12, - "y": 43 + "y": 51 }, "id": 59, "maxDataPoints": 10, @@ -1702,8 +1800,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1714,7 +1811,7 @@ "h": 6, "w": 5, "x": 0, - "y": 49 + "y": 57 }, "id": 17, "options": { @@ -1771,8 +1868,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "light-red", @@ -1787,7 +1883,7 @@ "h": 6, "w": 7, "x": 5, - "y": 49 + "y": 57 }, "id": 28, "options": { @@ -1871,8 +1967,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1887,7 +1982,7 @@ "h": 8, "w": 12, "x": 0, - "y": 55 + "y": 63 }, "id": 22, "options": { @@ -1937,8 +2032,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2043,7 +2137,7 @@ "h": 15, "w": 12, "x": 12, - "y": 61 + "y": 69 }, "id": 27, "maxDataPoints": 10, @@ -2190,8 +2284,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "#EAB839", @@ -2206,7 +2299,7 @@ "h": 7, "w": 12, "x": 0, - "y": 63 + "y": 71 }, "id": 20, "options": { @@ -2292,8 +2385,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2308,7 +2400,7 @@ "h": 9, "w": 12, "x": 0, - "y": 70 + "y": 78 }, "id": 24, "options": { @@ -2390,8 +2482,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2406,7 +2497,7 @@ "h": 12, "w": 12, "x": 12, - "y": 76 + "y": 84 }, "id": 25, "options": { @@ -2488,8 +2579,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2504,7 +2594,7 @@ "h": 10, "w": 12, "x": 0, - "y": 79 + "y": 87 }, "id": 26, "options": { @@ -2586,8 +2676,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2602,7 +2691,7 @@ "h": 9, "w": 12, "x": 12, - "y": 88 + "y": 96 }, "id": 30, "options": { @@ -2684,8 +2773,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2700,7 +2788,7 @@ "h": 8, "w": 6, "x": 0, - "y": 89 + "y": 97 }, "id": 23, "options": { @@ -2782,8 +2870,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2798,7 +2885,7 @@ "h": 8, "w": 6, "x": 6, - "y": 89 + "y": 97 }, "id": 60, "options": { @@ -2847,8 +2934,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -2859,7 +2945,7 @@ "h": 9, "w": 5, "x": 0, - "y": 97 + "y": 105 }, "id": 29, "options": { @@ -2945,8 +3031,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2961,7 +3046,7 @@ "h": 9, "w": 9, "x": 5, - "y": 97 + "y": 105 }, "id": 31, "options": { @@ -3043,8 +3128,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3059,7 +3143,7 @@ "h": 9, "w": 10, "x": 14, - "y": 97 + "y": 105 }, "id": 21, "options": { @@ -3098,7 +3182,7 @@ "h": 1, "w": 24, "x": 0, - "y": 106 + "y": 114 }, "id": 10, "panels": [], @@ -3154,8 +3238,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3170,7 +3253,7 @@ "h": 8, "w": 12, "x": 0, - "y": 107 + "y": 115 }, "id": 37, "options": { @@ -3252,8 +3335,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3268,7 +3350,7 @@ "h": 8, "w": 12, "x": 12, - "y": 107 + "y": 115 }, "id": 38, "options": { @@ -3352,8 +3434,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3368,7 +3449,7 @@ "h": 8, "w": 12, "x": 0, - "y": 115 + "y": 123 }, "id": 34, "options": { @@ -3450,8 +3531,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3466,7 +3546,7 @@ "h": 8, "w": 12, "x": 12, - "y": 115 + "y": 123 }, "id": 35, "options": { @@ -3548,8 +3628,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3564,7 +3643,7 @@ "h": 10, "w": 12, "x": 0, - "y": 123 + "y": 131 }, "id": 36, "options": { @@ -3646,8 +3725,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3662,7 +3740,7 @@ "h": 10, "w": 12, "x": 12, - "y": 123 + "y": 131 }, "id": 61, "options": { @@ -3701,7 +3779,7 @@ "h": 1, "w": 24, "x": 0, - "y": 133 + "y": 141 }, "id": 39, "panels": [], @@ -3733,8 +3811,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3749,7 +3826,7 @@ "h": 8, "w": 12, "x": 0, - "y": 134 + "y": 142 }, "id": 40, "maxDataPoints": 10, @@ -3830,8 +3907,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3846,7 +3922,7 @@ "h": 8, "w": 12, "x": 12, - "y": 134 + "y": 142 }, "id": 41, "options": { @@ -3881,7 +3957,7 @@ "h": 1, "w": 24, "x": 0, - "y": 142 + "y": 150 }, "id": 42, "panels": [], @@ -3904,8 +3980,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3920,7 +3995,7 @@ "h": 6, "w": 9, "x": 0, - "y": 143 + "y": 151 }, "id": 43, "options": { @@ -3980,8 +4055,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3996,7 +4070,7 @@ "h": 12, "w": 5, "x": 9, - "y": 143 + "y": 151 }, "id": 45, "options": { @@ -4047,8 +4121,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4063,7 +4136,7 @@ "h": 6, "w": 10, "x": 14, - "y": 143 + "y": 151 }, "id": 62, "options": { @@ -4123,8 +4196,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4139,7 +4211,7 @@ "h": 6, "w": 9, "x": 0, - "y": 149 + "y": 157 }, "id": 44, "options": { @@ -4199,8 +4271,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4215,7 +4286,7 @@ "h": 6, "w": 10, "x": 14, - "y": 149 + "y": 157 }, "id": 46, "options": { @@ -4275,8 +4346,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4291,7 +4361,7 @@ "h": 8, "w": 24, "x": 0, - "y": 155 + "y": 163 }, "id": 47, "options": { @@ -4353,8 +4423,7 @@ "mode": "absolute", "steps": [ { - "color": "#58585a", - "value": null + "color": "#58585a" }, { "color": "green", @@ -4369,7 +4438,7 @@ "h": 9, "w": 10, "x": 0, - "y": 163 + "y": 171 }, "id": 48, "maxDataPoints": 10, @@ -4454,8 +4523,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4470,7 +4538,7 @@ "h": 9, "w": 7, "x": 10, - "y": 163 + "y": 171 }, "id": 50, "options": { @@ -4552,8 +4620,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4568,7 +4635,7 @@ "h": 9, "w": 7, "x": 17, - "y": 163 + "y": 171 }, "id": 49, "options": { @@ -4618,8 +4685,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -4720,7 +4786,7 @@ "h": 16, "w": 12, "x": 0, - "y": 172 + "y": 180 }, "id": 64, "maxDataPoints": 10, @@ -4893,8 +4959,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4909,7 +4974,7 @@ "h": 8, "w": 12, "x": 12, - "y": 172 + "y": 180 }, "id": 52, "options": { @@ -4991,8 +5056,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -5007,7 +5071,7 @@ "h": 8, "w": 12, "x": 12, - "y": 180 + "y": 188 }, "id": 57, "options": { @@ -5162,7 +5226,7 @@ "h": 16, "w": 12, "x": 0, - "y": 188 + "y": 196 }, "id": 65, "maxDataPoints": 10, @@ -5357,7 +5421,7 @@ "h": 16, "w": 7, "x": 12, - "y": 188 + "y": 196 }, "id": 58, "options": { @@ -5454,7 +5518,7 @@ "h": 8, "w": 5, "x": 19, - "y": 188 + "y": 196 }, "id": 54, "options": { @@ -5518,7 +5582,7 @@ "h": 8, "w": 5, "x": 19, - "y": 196 + "y": 204 }, "id": 56, "options": { @@ -5619,7 +5683,7 @@ "h": 8, "w": 12, "x": 0, - "y": 204 + "y": 212 }, "id": 53, "options": { @@ -5716,7 +5780,7 @@ "h": 8, "w": 12, "x": 12, - "y": 204 + "y": 212 }, "id": 63, "options": { @@ -5752,7 +5816,7 @@ ], "preload": false, "refresh": "5s", - "schemaVersion": 40, + "schemaVersion": 41, "tags": [], "templating": { "list": [ @@ -5948,6 +6012,5 @@ "timezone": "browser", "title": "NGINX Plus", "uid": "fdris4hclbqiob", - "version": 20, - "weekStart": "" + "version": 9 } From 3011cd5eb89bbab43f200520133ec9b8f8c5657f Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Fri, 12 Sep 2025 15:03:02 +0100 Subject: [PATCH 09/13] expand test coverage --- internal/file/file_manager_service_test.go | 207 ++++++++++++++++++++- 1 file changed, 204 insertions(+), 3 deletions(-) diff --git a/internal/file/file_manager_service_test.go b/internal/file/file_manager_service_test.go index 01a0f7cda..275a0b192 100644 --- a/internal/file/file_manager_service_test.go +++ b/internal/file/file_manager_service_test.go @@ -8,6 +8,7 @@ package file import ( "context" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -68,6 +69,7 @@ func TestFileManagerService_ConfigApply_Add(t *testing.T) { assert.Equal(t, fileContent, data) assert.Equal(t, fileManagerService.fileActions[filePath].File, overview.GetFiles()[0]) assert.Equal(t, 1, fakeFileServiceClient.GetFileCallCount()) + assert.True(t, fileManagerService.rollbackManifest) } func TestFileManagerService_ConfigApply_Add_LargeFile(t *testing.T) { @@ -116,6 +118,7 @@ func TestFileManagerService_ConfigApply_Add_LargeFile(t *testing.T) { assert.Equal(t, fileManagerService.fileActions[filePath].File, overview.GetFiles()[0]) assert.Equal(t, 0, fakeFileServiceClient.GetFileCallCount()) assert.Equal(t, 53, int(fakeServerStreamingClient.currentChunkID)) + assert.True(t, fileManagerService.rollbackManifest) } func TestFileManagerService_ConfigApply_Update(t *testing.T) { @@ -176,6 +179,7 @@ func TestFileManagerService_ConfigApply_Update(t *testing.T) { assert.Equal(t, fileContent, data) assert.Equal(t, fileManagerService.rollbackFileContents[tempFile.Name()], previousFileContent) assert.Equal(t, fileManagerService.fileActions[tempFile.Name()].File, overview.GetFiles()[0]) + assert.True(t, fileManagerService.rollbackManifest) } func TestFileManagerService_ConfigApply_Delete(t *testing.T) { @@ -244,6 +248,43 @@ func TestFileManagerService_ConfigApply_Delete(t *testing.T) { filesOnDisk[tempFile.Name()].GetFileMeta().GetSize(), ) assert.Equal(t, model.OK, writeStatus) + assert.True(t, fileManagerService.rollbackManifest) +} + +func TestFileManagerService_ConfigApply_Failed(t *testing.T) { + ctx := t.Context() + tempDir := t.TempDir() + + filePath := filepath.Join(tempDir, "nginx.conf") + fileContent := []byte("# this is going to fail") + fileHash := files.GenerateHash(fileContent) + + overview := protos.FileOverview(filePath, fileHash) + + manifestDirPath := tempDir + manifestFilePath := manifestDirPath + "/manifest.json" + helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json") + + fakeFileServiceClient := &v1fakes.FakeFileServiceClient{} + fakeFileServiceClient.GetOverviewReturns(&mpi.GetOverviewResponse{ + Overview: overview, + }, nil) + fakeFileServiceClient.GetFileReturns(nil, errors.New("file not found")) + + agentConfig := types.AgentConfig() + agentConfig.AllowedDirectories = []string{tempDir} + + fileManagerService := NewFileManagerService(fakeFileServiceClient, agentConfig, &sync.RWMutex{}) + fileManagerService.agentConfig.ManifestDir = manifestDirPath + fileManagerService.manifestFilePath = manifestFilePath + + request := protos.CreateConfigApplyRequest(overview) + writeStatus, err := fileManagerService.ConfigApply(ctx, request) + + require.Error(t, err) + assert.Equal(t, model.RollbackRequired, writeStatus) + assert.False(t, fileManagerService.rollbackManifest) + } func TestFileManagerService_checkAllowedDirectory(t *testing.T) { @@ -571,7 +612,7 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) { for _, test := range tests { t.Run(test.name, func(tt *testing.T) { // Delete manifest file if it already exists - manifestFile := CreateTestManifestFile(t, tempDir, test.currentFiles) + manifestFile := CreateTestManifestFile(t, tempDir, test.currentFiles, true) defer manifestFile.Close() manifestDirPath := tempDir manifestFilePath := manifestFile.Name() @@ -595,11 +636,11 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) { } } -func CreateTestManifestFile(t testing.TB, tempDir string, currentFiles map[string]*mpi.File) *os.File { +func CreateTestManifestFile(t testing.TB, tempDir string, currentFiles map[string]*mpi.File, refrenced bool) *os.File { t.Helper() fakeFileServiceClient := &v1fakes.FakeFileServiceClient{} fileManagerService := NewFileManagerService(fakeFileServiceClient, types.AgentConfig(), &sync.RWMutex{}) - manifestFiles := fileManagerService.convertToManifestFileMap(currentFiles, true) + manifestFiles := fileManagerService.convertToManifestFileMap(currentFiles, refrenced) manifestJSON, err := json.MarshalIndent(manifestFiles, "", " ") require.NoError(t, err) file, err := os.CreateTemp(tempDir, "manifest.json") @@ -611,6 +652,166 @@ func CreateTestManifestFile(t testing.TB, tempDir string, currentFiles map[strin return file } +func TestFileManagerService_UpdateManifestFile(t *testing.T) { + ctx := t.Context() + fileContent := []byte("location /test {\n return 200 \"Test location\\n\";\n}") + fileHash := files.GenerateHash(fileContent) + + tests := []struct { + name string + currentFiles map[string]*mpi.File + referenced bool + previousRefrenced bool + expectedFiles map[string]*model.ManifestFile + currentManifestFiles map[string]*model.ManifestFile + }{ + { + name: "Test 1: Manifest file empty", + currentFiles: map[string]*mpi.File{ + "/etc/nginx/nginx.conf": { + FileMeta: protos.FileMeta("/etc/nginx/nginx.conf", fileHash), + }, + }, + expectedFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + }, + currentManifestFiles: map[string]*model.ManifestFile{}, + referenced: true, + previousRefrenced: true, + }, + { + name: "Test 2: Manifest file populated - unreferenced", + currentFiles: map[string]*mpi.File{ + "/etc/nginx/nginx.conf": { + FileMeta: protos.FileMeta("/etc/nginx/nginx.conf", fileHash), + }, + "/etc/nginx/unref.conf": { + FileMeta: protos.FileMeta("/etc/nginx/unref.conf", fileHash), + }, + }, + expectedFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + "/etc/nginx/unref.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/unref.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + }, + currentManifestFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + }, + referenced: false, + previousRefrenced: true, + }, + { + name: "Test 3: Manifest file populated - referenced", + currentFiles: map[string]*mpi.File{ + "/etc/nginx/nginx.conf": { + FileMeta: protos.FileMeta("/etc/nginx/nginx.conf", fileHash), + }, + "/etc/nginx/test.conf": { + FileMeta: protos.FileMeta("/etc/nginx/test.conf", fileHash), + }, + }, + expectedFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + "/etc/nginx/test.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/test.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + "/etc/nginx/unref.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/unref.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + }, + currentManifestFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + "/etc/nginx/unref.conf": { + &model.ManifestFileMeta{ + Name: "/etc/nginx/unref.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + }, + referenced: true, + previousRefrenced: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + manifestDirPath := t.TempDir() + file := helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json") + + fakeFileServiceClient := &v1fakes.FakeFileServiceClient{} + fileManagerService := NewFileManagerService(fakeFileServiceClient, types.AgentConfig(), &sync.RWMutex{}) + fileManagerService.agentConfig.ManifestDir = manifestDirPath + fileManagerService.manifestFilePath = file.Name() + + manifestJSON, err := json.MarshalIndent(test.currentManifestFiles, "", " ") + require.NoError(t, err) + + _, err = file.Write(manifestJSON) + require.NoError(t, err) + + updateErr := fileManagerService.UpdateManifestFile(ctx, test.currentFiles, test.referenced) + require.NoError(tt, updateErr) + + manifestFiles, _, manifestErr := fileManagerService.manifestFile() + require.NoError(tt, manifestErr) + assert.Equal(tt, test.expectedFiles, manifestFiles) + }) + } +} + func TestFileManagerService_fileActions(t *testing.T) { ctx := context.Background() tempDir := t.TempDir() From 7694085c59b5bde9a879fa22bfe105b0c1017488 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Fri, 12 Sep 2025 15:18:42 +0100 Subject: [PATCH 10/13] expand test coverage --- internal/file/file_manager_service_test.go | 39 +++++++++++----------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/file/file_manager_service_test.go b/internal/file/file_manager_service_test.go index 275a0b192..824730637 100644 --- a/internal/file/file_manager_service_test.go +++ b/internal/file/file_manager_service_test.go @@ -284,7 +284,6 @@ func TestFileManagerService_ConfigApply_Failed(t *testing.T) { require.Error(t, err) assert.Equal(t, model.RollbackRequired, writeStatus) assert.False(t, fileManagerService.rollbackManifest) - } func TestFileManagerService_checkAllowedDirectory(t *testing.T) { @@ -658,12 +657,12 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { fileHash := files.GenerateHash(fileContent) tests := []struct { - name string currentFiles map[string]*mpi.File - referenced bool - previousRefrenced bool - expectedFiles map[string]*model.ManifestFile currentManifestFiles map[string]*model.ManifestFile + expectedFiles map[string]*model.ManifestFile + name string + referenced bool + previousReferenced bool }{ { name: "Test 1: Manifest file empty", @@ -674,7 +673,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, expectedFiles: map[string]*model.ManifestFile{ "/etc/nginx/nginx.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/nginx.conf", Hash: fileHash, Size: 0, @@ -682,9 +681,9 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, }, - currentManifestFiles: map[string]*model.ManifestFile{}, + currentManifestFiles: make(map[string]*model.ManifestFile), referenced: true, - previousRefrenced: true, + previousReferenced: true, }, { name: "Test 2: Manifest file populated - unreferenced", @@ -698,7 +697,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, expectedFiles: map[string]*model.ManifestFile{ "/etc/nginx/nginx.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/nginx.conf", Hash: fileHash, Size: 0, @@ -706,7 +705,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, "/etc/nginx/unref.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/unref.conf", Hash: fileHash, Size: 0, @@ -716,7 +715,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, currentManifestFiles: map[string]*model.ManifestFile{ "/etc/nginx/nginx.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/nginx.conf", Hash: fileHash, Size: 0, @@ -724,8 +723,8 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, }, - referenced: false, - previousRefrenced: true, + referenced: false, + previousReferenced: true, }, { name: "Test 3: Manifest file populated - referenced", @@ -739,7 +738,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, expectedFiles: map[string]*model.ManifestFile{ "/etc/nginx/nginx.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/nginx.conf", Hash: fileHash, Size: 0, @@ -747,7 +746,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, "/etc/nginx/test.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/test.conf", Hash: fileHash, Size: 0, @@ -755,7 +754,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, "/etc/nginx/unref.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/unref.conf", Hash: fileHash, Size: 0, @@ -765,7 +764,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, currentManifestFiles: map[string]*model.ManifestFile{ "/etc/nginx/nginx.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/nginx.conf", Hash: fileHash, Size: 0, @@ -773,7 +772,7 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, "/etc/nginx/unref.conf": { - &model.ManifestFileMeta{ + ManifestFileMeta: &model.ManifestFileMeta{ Name: "/etc/nginx/unref.conf", Hash: fileHash, Size: 0, @@ -781,8 +780,8 @@ func TestFileManagerService_UpdateManifestFile(t *testing.T) { }, }, }, - referenced: true, - previousRefrenced: false, + referenced: true, + previousReferenced: false, }, } From be83f587938b367bc6a71da8b77074e1748c8c76 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Mon, 15 Sep 2025 10:12:35 +0100 Subject: [PATCH 11/13] draft of refactor --- .../nginxplusreceiver/record/cache_metrics.go | 108 ++ .../nginxplusreceiver/record/http_metrics.go | 114 ++ .../record/location_zone_metrics.go | 178 +++ .../record/server_zone_metrics.go | 165 +++ .../nginxplusreceiver/record/slab_metrics.go | 48 + .../nginxplusreceiver/record/ssl_metrics.go | 80 ++ .../record/stream_metrics.go | 265 ++++ .../record/upstream_metrics.go | 302 +++++ .../collector/nginxplusreceiver/scraper.go | 1136 +---------------- .../nginxplusreceiver/scraper_test.go | 31 +- 10 files changed, 1300 insertions(+), 1127 deletions(-) create mode 100644 internal/collector/nginxplusreceiver/record/cache_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/http_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/location_zone_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/server_zone_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/slab_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/ssl_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/stream_metrics.go create mode 100644 internal/collector/nginxplusreceiver/record/upstream_metrics.go diff --git a/internal/collector/nginxplusreceiver/record/cache_metrics.go b/internal/collector/nginxplusreceiver/record/cache_metrics.go new file mode 100644 index 000000000..caac89757 --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/cache_metrics.go @@ -0,0 +1,108 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +func RecordCacheMetrics(mb *metadata.MetricsBuilder, stats *plusapi.Stats, now pcommon.Timestamp) { + for name, cache := range stats.Caches { + // Cache Bytes + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Bypass.Bytes), + metadata.AttributeNginxCacheOutcomeBYPASS, + name, + ) + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Expired.Bytes), + metadata.AttributeNginxCacheOutcomeEXPIRED, + name, + ) + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Hit.Bytes), + metadata.AttributeNginxCacheOutcomeHIT, + name, + ) + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Miss.Bytes), + metadata.AttributeNginxCacheOutcomeMISS, + name, + ) + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Revalidated.Bytes), + metadata.AttributeNginxCacheOutcomeREVALIDATED, + name, + ) + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Stale.Bytes), + metadata.AttributeNginxCacheOutcomeSTALE, + name, + ) + mb.RecordNginxCacheBytesReadDataPoint( + now, + int64(cache.Updating.Bytes), + metadata.AttributeNginxCacheOutcomeUPDATING, + name, + ) + + // Cache Memory + mb.RecordNginxCacheMemoryLimitDataPoint(now, int64(cache.MaxSize), name) + mb.RecordNginxCacheMemoryUsageDataPoint(now, int64(cache.Size), name) + + // Cache Responses + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Bypass.Responses), + metadata.AttributeNginxCacheOutcomeBYPASS, + name, + ) + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Expired.Responses), + metadata.AttributeNginxCacheOutcomeEXPIRED, + name, + ) + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Hit.Responses), + metadata.AttributeNginxCacheOutcomeHIT, + name, + ) + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Miss.Responses), + metadata.AttributeNginxCacheOutcomeMISS, + name, + ) + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Revalidated.Responses), + metadata.AttributeNginxCacheOutcomeREVALIDATED, + name, + ) + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Stale.Responses), + metadata.AttributeNginxCacheOutcomeSTALE, + name, + ) + mb.RecordNginxCacheResponsesDataPoint( + now, + int64(cache.Updating.Responses), + metadata.AttributeNginxCacheOutcomeUPDATING, + name, + ) + } +} diff --git a/internal/collector/nginxplusreceiver/record/http_metrics.go b/internal/collector/nginxplusreceiver/record/http_metrics.go new file mode 100644 index 000000000..b79493586 --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/http_metrics.go @@ -0,0 +1,114 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +type HTTPMetrics struct { + mb *metadata.MetricsBuilder + PreviousHTTPRequestsTotal uint64 +} + +func NewHTTPMetrics(stats *plusapi.Stats, mb *metadata.MetricsBuilder) *HTTPMetrics { + return &HTTPMetrics{ + mb: mb, + PreviousHTTPRequestsTotal: stats.HTTPRequests.Total, + } +} + +func (hm *HTTPMetrics) RecordHTTPMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { + // Requests + hm.mb.RecordNginxHTTPRequestsDataPoint(now, int64(stats.HTTPRequests.Total), "", 0) + + // Request Count + requestsDiff := int64(stats.HTTPRequests.Total) - int64(hm.PreviousHTTPRequestsTotal) + hm.mb.RecordNginxHTTPRequestCountDataPoint(now, requestsDiff, "", 0) + hm.PreviousHTTPRequestsTotal = stats.HTTPRequests.Total + + // Connections + hm.mb.RecordNginxHTTPConnectionsDataPoint( + now, + int64(stats.Connections.Accepted), + metadata.AttributeNginxConnectionsOutcomeACCEPTED, + ) + hm.mb.RecordNginxHTTPConnectionsDataPoint( + now, + int64(stats.Connections.Dropped), + metadata.AttributeNginxConnectionsOutcomeDROPPED, + ) + hm.mb.RecordNginxHTTPConnectionCountDataPoint( + now, + int64(stats.Connections.Active), + metadata.AttributeNginxConnectionsOutcomeACTIVE, + ) + hm.mb.RecordNginxHTTPConnectionCountDataPoint( + now, + int64(stats.Connections.Idle), + metadata.AttributeNginxConnectionsOutcomeIDLE, + ) +} + +func (hm *HTTPMetrics) RecordHTTPLimitMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { + // Limit Connections + for name, limitConnection := range stats.HTTPLimitConnections { + hm.mb.RecordNginxHTTPLimitConnRequestsDataPoint( + now, + int64(limitConnection.Passed), + metadata.AttributeNginxLimitConnOutcomePASSED, + name, + ) + hm.mb.RecordNginxHTTPLimitConnRequestsDataPoint( + now, + int64(limitConnection.Rejected), + metadata.AttributeNginxLimitConnOutcomeREJECTED, + name, + ) + hm.mb.RecordNginxHTTPLimitConnRequestsDataPoint( + now, + int64(limitConnection.RejectedDryRun), + metadata.AttributeNginxLimitConnOutcomeREJECTEDDRYRUN, + name, + ) + } + + // Limit Requests + for name, limitRequest := range stats.HTTPLimitRequests { + hm.mb.RecordNginxHTTPLimitReqRequestsDataPoint( + now, + int64(limitRequest.Passed), + metadata.AttributeNginxLimitReqOutcomePASSED, + name, + ) + hm.mb.RecordNginxHTTPLimitReqRequestsDataPoint( + now, + int64(limitRequest.Rejected), + metadata.AttributeNginxLimitReqOutcomeREJECTED, + name, + ) + hm.mb.RecordNginxHTTPLimitReqRequestsDataPoint( + now, + int64(limitRequest.RejectedDryRun), + metadata.AttributeNginxLimitReqOutcomeREJECTEDDRYRUN, + name, + ) + hm.mb.RecordNginxHTTPLimitReqRequestsDataPoint( + now, + int64(limitRequest.Delayed), + metadata.AttributeNginxLimitReqOutcomeDELAYED, + name, + ) + hm.mb.RecordNginxHTTPLimitReqRequestsDataPoint( + now, + int64(limitRequest.DelayedDryRun), + metadata.AttributeNginxLimitReqOutcomeDELAYEDDRYRUN, + name, + ) + } +} diff --git a/internal/collector/nginxplusreceiver/record/location_zone_metrics.go b/internal/collector/nginxplusreceiver/record/location_zone_metrics.go new file mode 100644 index 000000000..e4d1013ad --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/location_zone_metrics.go @@ -0,0 +1,178 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +type LocationZoneMetrics struct { + PreviousLocationZoneResponses map[string]ResponseStatuses + PreviousLocationZoneRequests map[string]int64 + mb *metadata.MetricsBuilder +} + +type ResponseStatuses struct { + OneHundredStatusRange int64 + TwoHundredStatusRange int64 + ThreeHundredStatusRange int64 + FourHundredStatusRange int64 + FiveHundredStatusRange int64 +} + +func NewLocationZoneMetrics(stats *plusapi.Stats, mb *metadata.MetricsBuilder) *LocationZoneMetrics { + return &LocationZoneMetrics{ + mb: mb, + PreviousLocationZoneResponses: createPreviousLocationZoneResponses(stats), + PreviousLocationZoneRequests: createPreviousLocationZoneRequests(stats), + } +} + +func (lzm *LocationZoneMetrics) RecordLocationZoneMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { + for lzName, lz := range stats.LocationZones { + // Requests + lzm.mb.RecordNginxHTTPRequestIoDataPoint( + now, + lz.Received, + metadata.AttributeNginxIoDirectionReceive, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + lzm.mb.RecordNginxHTTPRequestIoDataPoint( + now, + lz.Sent, + metadata.AttributeNginxIoDirectionTransmit, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + lzm.mb.RecordNginxHTTPRequestsDataPoint( + now, + lz.Requests, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + lzm.mb.RecordNginxHTTPRequestCountDataPoint(now, + lz.Requests-lzm.PreviousLocationZoneRequests[lzName], + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + lzm.mb.RecordNginxHTTPRequestDiscardedDataPoint(now, lz.Discarded, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + lzm.PreviousLocationZoneRequests[lzName] = lz.Requests + + lzm.recordLocationZoneHTTPMetrics(lz, lzName, now) + } +} + +//nolint:dupl // Duplicate of recordServerZoneHTTPMetrics but same function can not be used due to plusapi.LocationZone +func (lzm *LocationZoneMetrics) recordLocationZoneHTTPMetrics(lz plusapi.LocationZone, + lzName string, now pcommon.Timestamp, +) { + // Response Status + lzm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses1xx), + metadata.AttributeNginxStatusRange1xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + lzm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses2xx), + metadata.AttributeNginxStatusRange2xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + lzm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses3xx), + metadata.AttributeNginxStatusRange3xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + lzm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses4xx), + metadata.AttributeNginxStatusRange4xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + lzm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses5xx), + metadata.AttributeNginxStatusRange5xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION, + ) + + // Requests + lzm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(lz.Responses.Responses1xx)-lzm.PreviousLocationZoneResponses[lzName].OneHundredStatusRange, + metadata.AttributeNginxStatusRange1xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION) + + // Response Count Status + lzm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(lz.Responses.Responses2xx)-lzm.PreviousLocationZoneResponses[lzName].TwoHundredStatusRange, + metadata.AttributeNginxStatusRange2xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION) + + lzm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(lz.Responses.Responses3xx)-lzm.PreviousLocationZoneResponses[lzName].ThreeHundredStatusRange, + metadata.AttributeNginxStatusRange3xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION) + + lzm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(lz.Responses.Responses4xx)-lzm.PreviousLocationZoneResponses[lzName].FourHundredStatusRange, + metadata.AttributeNginxStatusRange4xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION) + + lzm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(lz.Responses.Responses5xx)-lzm.PreviousLocationZoneResponses[lzName].FiveHundredStatusRange, + metadata.AttributeNginxStatusRange5xx, + lzName, + metadata.AttributeNginxZoneTypeLOCATION) + + respStatus := ResponseStatuses{ + OneHundredStatusRange: int64(lz.Responses.Responses1xx), + TwoHundredStatusRange: int64(lz.Responses.Responses2xx), + ThreeHundredStatusRange: int64(lz.Responses.Responses3xx), + FourHundredStatusRange: int64(lz.Responses.Responses4xx), + FiveHundredStatusRange: int64(lz.Responses.Responses5xx), + } + + lzm.PreviousLocationZoneResponses[lzName] = respStatus +} + +func createPreviousLocationZoneResponses(stats *plusapi.Stats) map[string]ResponseStatuses { + previousLocationZoneResponses := make(map[string]ResponseStatuses) + for lzName, lz := range stats.LocationZones { + respStatus := ResponseStatuses{ + OneHundredStatusRange: int64(lz.Responses.Responses1xx), + TwoHundredStatusRange: int64(lz.Responses.Responses2xx), + ThreeHundredStatusRange: int64(lz.Responses.Responses3xx), + FourHundredStatusRange: int64(lz.Responses.Responses4xx), + FiveHundredStatusRange: int64(lz.Responses.Responses5xx), + } + + previousLocationZoneResponses[lzName] = respStatus + } + + return previousLocationZoneResponses +} + +func createPreviousLocationZoneRequests(stats *plusapi.Stats) map[string]int64 { + previousLocationZoneRequests := make(map[string]int64) + for lzName, lz := range stats.LocationZones { + previousLocationZoneRequests[lzName] = lz.Requests + } + + return previousLocationZoneRequests +} diff --git a/internal/collector/nginxplusreceiver/record/server_zone_metrics.go b/internal/collector/nginxplusreceiver/record/server_zone_metrics.go new file mode 100644 index 000000000..939542d37 --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/server_zone_metrics.go @@ -0,0 +1,165 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +type ServerZoneMetrics struct { + PreviousServerZoneResponses map[string]ResponseStatuses + PreviousServerZoneRequests map[string]int64 + mb *metadata.MetricsBuilder +} + +func NewServerZoneMetrics(stats *plusapi.Stats, mb *metadata.MetricsBuilder) *ServerZoneMetrics { + return &ServerZoneMetrics{ + mb: mb, + PreviousServerZoneResponses: createPreviousServerZoneResponses(stats), + PreviousServerZoneRequests: createPreviousServerZoneRequests(stats), + } +} + +func (szm *ServerZoneMetrics) RecordServerZoneMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { + for szName, sz := range stats.ServerZones { + szm.mb.RecordNginxHTTPRequestIoDataPoint( + now, + int64(sz.Received), + metadata.AttributeNginxIoDirectionReceive, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + szm.mb.RecordNginxHTTPRequestIoDataPoint( + now, + int64(sz.Sent), + metadata.AttributeNginxIoDirectionTransmit, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + + szm.mb.RecordNginxHTTPRequestsDataPoint(now, int64(sz.Requests), szName, metadata.AttributeNginxZoneTypeSERVER) + + szm.mb.RecordNginxHTTPRequestCountDataPoint(now, + int64(sz.Requests)-szm.PreviousServerZoneRequests[szName], + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + szm.PreviousServerZoneRequests[szName] = int64(sz.Requests) + + szm.mb.RecordNginxHTTPRequestDiscardedDataPoint(now, int64(sz.Discarded), + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + + szm.mb.RecordNginxHTTPRequestProcessingCountDataPoint(now, int64(sz.Processing), + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + + szm.recordServerZoneHTTPMetrics(sz, szName, now) + } +} + +//nolint:dupl // Duplicate of recordLocationZoneHTTPMetrics but same function can not be used due to plusapi.ServerZone +func (szm *ServerZoneMetrics) recordServerZoneHTTPMetrics(sz plusapi.ServerZone, szName string, now pcommon.Timestamp) { + // Response Status + szm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses1xx), + metadata.AttributeNginxStatusRange1xx, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + szm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses2xx), + metadata.AttributeNginxStatusRange2xx, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + szm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses3xx), + metadata.AttributeNginxStatusRange3xx, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + + szm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses4xx), + metadata.AttributeNginxStatusRange4xx, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + + szm.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses5xx), + metadata.AttributeNginxStatusRange5xx, + szName, + metadata.AttributeNginxZoneTypeSERVER, + ) + + // Response Count Status + szm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(sz.Responses.Responses1xx)-szm.PreviousServerZoneResponses[szName].OneHundredStatusRange, + metadata.AttributeNginxStatusRange1xx, + szName, + metadata.AttributeNginxZoneTypeSERVER) + + szm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(sz.Responses.Responses2xx)-szm.PreviousServerZoneResponses[szName].TwoHundredStatusRange, + metadata.AttributeNginxStatusRange2xx, + szName, + metadata.AttributeNginxZoneTypeSERVER) + + szm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(sz.Responses.Responses3xx)-szm.PreviousServerZoneResponses[szName].ThreeHundredStatusRange, + metadata.AttributeNginxStatusRange3xx, + szName, + metadata.AttributeNginxZoneTypeSERVER) + + szm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(sz.Responses.Responses4xx)-szm.PreviousServerZoneResponses[szName].FourHundredStatusRange, + metadata.AttributeNginxStatusRange4xx, + szName, + metadata.AttributeNginxZoneTypeSERVER) + + szm.mb.RecordNginxHTTPResponseCountDataPoint(now, + int64(sz.Responses.Responses5xx)-szm.PreviousServerZoneResponses[szName].FiveHundredStatusRange, + metadata.AttributeNginxStatusRange5xx, + szName, + metadata.AttributeNginxZoneTypeSERVER) + + respStatus := ResponseStatuses{ + OneHundredStatusRange: int64(sz.Responses.Responses1xx), + TwoHundredStatusRange: int64(sz.Responses.Responses2xx), + ThreeHundredStatusRange: int64(sz.Responses.Responses3xx), + FourHundredStatusRange: int64(sz.Responses.Responses4xx), + FiveHundredStatusRange: int64(sz.Responses.Responses5xx), + } + + szm.PreviousServerZoneResponses[szName] = respStatus +} + +func createPreviousServerZoneRequests(stats *plusapi.Stats) map[string]int64 { + previousServerZoneRequests := make(map[string]int64) + for szName, sz := range stats.ServerZones { + previousServerZoneRequests[szName] = int64(sz.Requests) + } + + return previousServerZoneRequests +} + +func createPreviousServerZoneResponses(stats *plusapi.Stats) map[string]ResponseStatuses { + previousServerZoneResponses := make(map[string]ResponseStatuses) + for szName, sz := range stats.ServerZones { + respStatus := ResponseStatuses{ + OneHundredStatusRange: int64(sz.Responses.Responses1xx), + TwoHundredStatusRange: int64(sz.Responses.Responses2xx), + ThreeHundredStatusRange: int64(sz.Responses.Responses3xx), + FourHundredStatusRange: int64(sz.Responses.Responses4xx), + FiveHundredStatusRange: int64(sz.Responses.Responses5xx), + } + + previousServerZoneResponses[szName] = respStatus + } + + return previousServerZoneResponses +} diff --git a/internal/collector/nginxplusreceiver/record/slab_metrics.go b/internal/collector/nginxplusreceiver/record/slab_metrics.go new file mode 100644 index 000000000..5ca0eb76f --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/slab_metrics.go @@ -0,0 +1,48 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "strconv" + + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.uber.org/zap" +) + +func RecordSlabPageMetrics(mb *metadata.MetricsBuilder, stats *plusapi.Stats, now pcommon.Timestamp, + logger *zap.Logger, +) { + for name, slab := range stats.Slabs { + mb.RecordNginxSlabPageFreeDataPoint(now, int64(slab.Pages.Free), name) + mb.RecordNginxSlabPageUsageDataPoint(now, int64(slab.Pages.Used), name) + + for slotName, slot := range slab.Slots { + slotNumber, err := strconv.ParseInt(slotName, 10, 64) + if err != nil { + logger.Warn("Invalid slot name for NGINX Plus slab metrics", zap.Error(err)) + } + + mb.RecordNginxSlabSlotUsageDataPoint(now, int64(slot.Used), slotNumber, name) + mb.RecordNginxSlabSlotFreeDataPoint(now, int64(slot.Free), slotNumber, name) + mb.RecordNginxSlabSlotAllocationsDataPoint( + now, + int64(slot.Fails), + slotNumber, + metadata.AttributeNginxSlabSlotAllocationResultFAILURE, + name, + ) + mb.RecordNginxSlabSlotAllocationsDataPoint( + now, + int64(slot.Reqs), + slotNumber, + metadata.AttributeNginxSlabSlotAllocationResultSUCCESS, + name, + ) + } + } +} diff --git a/internal/collector/nginxplusreceiver/record/ssl_metrics.go b/internal/collector/nginxplusreceiver/record/ssl_metrics.go new file mode 100644 index 000000000..e8360dd80 --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/ssl_metrics.go @@ -0,0 +1,80 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +func RecordSSLMetrics(mb *metadata.MetricsBuilder, now pcommon.Timestamp, stats *plusapi.Stats) { + // SSL Handshake + mb.RecordNginxSslHandshakesDataPoint( + now, + int64(stats.SSL.HandshakesFailed), + metadata.AttributeNginxSslStatusFAILED, + 0, + ) + mb.RecordNginxSslHandshakesDataPoint(now, int64(stats.SSL.Handshakes), 0, 0) + mb.RecordNginxSslHandshakesDataPoint( + now, + int64(stats.SSL.SessionReuses), + metadata.AttributeNginxSslStatusREUSE, + 0, + ) + mb.RecordNginxSslHandshakesDataPoint( + now, + int64(stats.SSL.NoCommonProtocol), + metadata.AttributeNginxSslStatusFAILED, + metadata.AttributeNginxSslHandshakeReasonNOCOMMONPROTOCOL, + ) + mb.RecordNginxSslHandshakesDataPoint( + now, + int64(stats.SSL.NoCommonCipher), + metadata.AttributeNginxSslStatusFAILED, + metadata.AttributeNginxSslHandshakeReasonNOCOMMONCIPHER, + ) + mb.RecordNginxSslHandshakesDataPoint( + now, + int64(stats.SSL.HandshakeTimeout), + metadata.AttributeNginxSslStatusFAILED, + metadata.AttributeNginxSslHandshakeReasonTIMEOUT, + ) + mb.RecordNginxSslHandshakesDataPoint( + now, + int64(stats.SSL.PeerRejectedCert), + metadata.AttributeNginxSslStatusFAILED, + metadata.AttributeNginxSslHandshakeReasonCERTREJECTED, + ) + + // SSL Certificate + mb.RecordNginxSslCertificateVerifyFailuresDataPoint( + now, + int64(stats.SSL.VerifyFailures.NoCert), + metadata.AttributeNginxSslVerifyFailureReasonNOCERT, + ) + mb.RecordNginxSslCertificateVerifyFailuresDataPoint( + now, + int64(stats.SSL.VerifyFailures.ExpiredCert), + metadata.AttributeNginxSslVerifyFailureReasonEXPIREDCERT, + ) + mb.RecordNginxSslCertificateVerifyFailuresDataPoint( + now, + int64(stats.SSL.VerifyFailures.RevokedCert), + metadata.AttributeNginxSslVerifyFailureReasonREVOKEDCERT, + ) + mb.RecordNginxSslCertificateVerifyFailuresDataPoint( + now, + int64(stats.SSL.VerifyFailures.HostnameMismatch), + metadata.AttributeNginxSslVerifyFailureReasonHOSTNAMEMISMATCH, + ) + mb.RecordNginxSslCertificateVerifyFailuresDataPoint( + now, + int64(stats.SSL.VerifyFailures.Other), + metadata.AttributeNginxSslVerifyFailureReasonOTHER, + ) +} diff --git a/internal/collector/nginxplusreceiver/record/stream_metrics.go b/internal/collector/nginxplusreceiver/record/stream_metrics.go new file mode 100644 index 000000000..d7aa97bb4 --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/stream_metrics.go @@ -0,0 +1,265 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +func RecordStreamMetrics(mb *metadata.MetricsBuilder, stats *plusapi.Stats, now pcommon.Timestamp) { + for name, streamServerZone := range stats.StreamServerZones { + mb.RecordNginxStreamIoDataPoint( + now, + int64(streamServerZone.Received), + metadata.AttributeNginxIoDirectionReceive, + name, + ) + mb.RecordNginxStreamIoDataPoint( + now, + int64(streamServerZone.Sent), + metadata.AttributeNginxIoDirectionTransmit, + name, + ) + // Connection + mb.RecordNginxStreamConnectionAcceptedDataPoint(now, int64(streamServerZone.Connections), name) + mb.RecordNginxStreamConnectionDiscardedDataPoint(now, int64(streamServerZone.Discarded), name) + mb.RecordNginxStreamConnectionProcessingCountDataPoint(now, int64(streamServerZone.Processing), name) + + // Stream + mb.RecordNginxStreamSessionStatusDataPoint( + now, + int64(streamServerZone.Sessions.Sessions2xx), + metadata.AttributeNginxStatusRange2xx, + name, + ) + mb.RecordNginxStreamSessionStatusDataPoint( + now, + int64(streamServerZone.Sessions.Sessions4xx), + metadata.AttributeNginxStatusRange4xx, + name, + ) + mb.RecordNginxStreamSessionStatusDataPoint( + now, + int64(streamServerZone.Sessions.Sessions5xx), + metadata.AttributeNginxStatusRange5xx, + name, + ) + mb.RecordNginxStreamSessionStatusDataPoint(now, int64(streamServerZone.Sessions.Total), 0, name) + } + + // Stream Upstreams + for upstreamName, upstream := range stats.StreamUpstreams { + peerStates := make(map[string]int) + + for _, peer := range upstream.Peers { + mb.RecordNginxStreamUpstreamPeerIoDataPoint( + now, + int64(peer.Received), + metadata.AttributeNginxIoDirectionReceive, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerIoDataPoint( + now, + int64(peer.Sent), + metadata.AttributeNginxIoDirectionTransmit, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + // Connection + mb.RecordNginxStreamUpstreamPeerConnectionCountDataPoint( + now, + int64(peer.Active), + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerConnectionTimeDataPoint( + now, + int64(peer.ConnectTime), + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + + mb.RecordNginxStreamUpstreamPeerConnectionsDataPoint( + now, + int64(peer.Connections), + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + + // Health + mb.RecordNginxStreamUpstreamPeerHealthChecksDataPoint( + now, + int64(peer.HealthChecks.Checks), + 0, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerHealthChecksDataPoint( + now, + int64(peer.HealthChecks.Fails), + metadata.AttributeNginxHealthCheckFAIL, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerHealthChecksDataPoint( + now, + int64(peer.HealthChecks.Unhealthy), + metadata.AttributeNginxHealthCheckUNHEALTHY, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + + // Response + mb.RecordNginxStreamUpstreamPeerResponseTimeDataPoint( + now, + int64(peer.ResponseTime), + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerTtfbTimeDataPoint( + now, + int64(peer.FirstByteTime), + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerUnavailablesDataPoint( + now, + int64(peer.Unavail), + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + + // State + mb.RecordNginxStreamUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateChecking), + metadata.AttributeNginxPeerStateCHECKING, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateDown), + metadata.AttributeNginxPeerStateDOWN, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateDraining), + metadata.AttributeNginxPeerStateDRAINING, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateUnavail), + metadata.AttributeNginxPeerStateUNAVAILABLE, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateUnhealthy), + metadata.AttributeNginxPeerStateUNHEALTHY, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + mb.RecordNginxStreamUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateUp), + metadata.AttributeNginxPeerStateUP, + upstream.Zone, + upstreamName, + peer.Server, + peer.Name, + ) + + peerStates[peer.State]++ + } + + // Peer Count + mb.RecordNginxStreamUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateChecking]), + metadata.AttributeNginxPeerStateCHECKING, + upstream.Zone, + upstreamName, + ) + mb.RecordNginxStreamUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateDown]), + metadata.AttributeNginxPeerStateDOWN, + upstream.Zone, + upstreamName, + ) + mb.RecordNginxStreamUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateDraining]), + metadata.AttributeNginxPeerStateDRAINING, + upstream.Zone, + upstreamName, + ) + mb.RecordNginxStreamUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateUnavail]), + metadata.AttributeNginxPeerStateUNAVAILABLE, + upstream.Zone, + upstreamName, + ) + mb.RecordNginxStreamUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateUnhealthy]), + metadata.AttributeNginxPeerStateUNHEALTHY, + upstream.Zone, + upstreamName, + ) + mb.RecordNginxStreamUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateUp]), + metadata.AttributeNginxPeerStateUP, + upstream.Zone, + upstreamName, + ) + + mb.RecordNginxStreamUpstreamZombieCountDataPoint(now, int64(upstream.Zombies), upstream.Zone, upstreamName) + } +} diff --git a/internal/collector/nginxplusreceiver/record/upstream_metrics.go b/internal/collector/nginxplusreceiver/record/upstream_metrics.go new file mode 100644 index 000000000..0c06ca515 --- /dev/null +++ b/internal/collector/nginxplusreceiver/record/upstream_metrics.go @@ -0,0 +1,302 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +package record + +import ( + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/internal/metadata" + plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +const ( + // Peer state is one of “up”, “draining”, “down”, “unavail”, “checking”, and “unhealthy”. + peerStateUp = "up" + peerStateDraining = "draining" + peerStateDown = "down" + peerStateUnavail = "unavail" + peerStateChecking = "checking" + peerStateUnhealthy = "unhealthy" +) + +func RecordHTTPUpstreamPeerMetrics(mb *metadata.MetricsBuilder, stats *plusapi.Stats, now pcommon.Timestamp) { + for name, upstream := range stats.Upstreams { + mb.RecordNginxHTTPUpstreamKeepaliveCountDataPoint(now, int64(upstream.Keepalive), upstream.Zone, name) + + peerStates := make(map[string]int) + + for _, peer := range upstream.Peers { + mb.RecordNginxHTTPUpstreamPeerIoDataPoint( + now, + int64(peer.Received), + metadata.AttributeNginxIoDirectionReceive, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerIoDataPoint( + now, + int64(peer.Sent), + metadata.AttributeNginxIoDirectionTransmit, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerConnectionCountDataPoint( + now, + int64(peer.Active), + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerFailsDataPoint( + now, + int64(peer.Fails), + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerHeaderTimeDataPoint( + now, + int64(peer.HeaderTime), + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerHealthChecksDataPoint( + now, + int64(peer.HealthChecks.Checks), + 0, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerHealthChecksDataPoint( + now, + int64(peer.HealthChecks.Fails), + metadata.AttributeNginxHealthCheckFAIL, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerHealthChecksDataPoint( + now, + int64(peer.HealthChecks.Unhealthy), + metadata.AttributeNginxHealthCheckUNHEALTHY, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerRequestsDataPoint( + now, + int64(peer.Requests), + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerResponseTimeDataPoint( + now, + int64(peer.ResponseTime), + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( + now, + int64(peer.Responses.Total), + 0, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( + now, + int64(peer.Responses.Responses1xx), + metadata.AttributeNginxStatusRange1xx, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( + now, + int64(peer.Responses.Responses2xx), + metadata.AttributeNginxStatusRange2xx, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( + now, + int64(peer.Responses.Responses3xx), + metadata.AttributeNginxStatusRange3xx, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( + now, + int64(peer.Responses.Responses4xx), + metadata.AttributeNginxStatusRange4xx, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( + now, + int64(peer.Responses.Responses5xx), + metadata.AttributeNginxStatusRange5xx, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerUnavailablesDataPoint( + now, + int64(peer.Unavail), + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + mb.RecordNginxHTTPUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateChecking), + metadata.AttributeNginxPeerStateCHECKING, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateDown), + metadata.AttributeNginxPeerStateDOWN, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateDraining), + metadata.AttributeNginxPeerStateDRAINING, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateUnavail), + metadata.AttributeNginxPeerStateUNAVAILABLE, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateUnhealthy), + metadata.AttributeNginxPeerStateUNHEALTHY, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + mb.RecordNginxHTTPUpstreamPeerStateDataPoint( + now, + boolToInt64(peer.State == peerStateUp), + metadata.AttributeNginxPeerStateUP, + upstream.Zone, + name, + peer.Server, + peer.Name, + ) + + peerStates[peer.State]++ + } + + // Peer Count + mb.RecordNginxHTTPUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateChecking]), + metadata.AttributeNginxPeerStateCHECKING, + upstream.Zone, + name, + ) + mb.RecordNginxHTTPUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateDown]), + metadata.AttributeNginxPeerStateDOWN, + upstream.Zone, + name, + ) + mb.RecordNginxHTTPUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateDraining]), + metadata.AttributeNginxPeerStateDRAINING, + upstream.Zone, + name, + ) + mb.RecordNginxHTTPUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateUnavail]), + metadata.AttributeNginxPeerStateUNAVAILABLE, + upstream.Zone, + name, + ) + mb.RecordNginxHTTPUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateUnhealthy]), + metadata.AttributeNginxPeerStateUNHEALTHY, + upstream.Zone, + name, + ) + mb.RecordNginxHTTPUpstreamPeerCountDataPoint( + now, + int64(peerStates[peerStateUp]), + metadata.AttributeNginxPeerStateUP, + upstream.Zone, + name, + ) + + // Upstream Queue + mb.RecordNginxHTTPUpstreamQueueLimitDataPoint(now, int64(upstream.Queue.MaxSize), upstream.Zone, name) + mb.RecordNginxHTTPUpstreamQueueOverflowsDataPoint(now, int64(upstream.Queue.Overflows), upstream.Zone, name) + mb.RecordNginxHTTPUpstreamQueueUsageDataPoint(now, int64(upstream.Queue.Size), upstream.Zone, name) + mb.RecordNginxHTTPUpstreamZombieCountDataPoint(now, int64(upstream.Zombies), upstream.Zone, name) + } +} + +//nolint:revive // booleanValue flag is mandatory +func boolToInt64(booleanValue bool) int64 { + if booleanValue { + return 1 + } + + return 0 +} diff --git a/internal/collector/nginxplusreceiver/scraper.go b/internal/collector/nginxplusreceiver/scraper.go index e677e2451..165545e26 100644 --- a/internal/collector/nginxplusreceiver/scraper.go +++ b/internal/collector/nginxplusreceiver/scraper.go @@ -2,6 +2,7 @@ // // This source code is licensed under the Apache License, Version 2.0 license found in the // LICENSE file in the root directory of this source tree. + package nginxplusreceiver import ( @@ -12,11 +13,11 @@ import ( "net" "net/http" "os" - "strconv" "strings" "sync" "time" + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/record" "go.opentelemetry.io/collector/component" "go.uber.org/zap" @@ -29,37 +30,17 @@ import ( plusapi "github.com/nginxinc/nginx-plus-go-client/v2/client" ) -const ( - // Peer state is one of “up”, “draining”, “down”, “unavail”, “checking”, and “unhealthy”. - peerStateUp = "up" - peerStateDraining = "draining" - peerStateDown = "down" - peerStateUnavail = "unavail" - peerStateChecking = "checking" - peerStateUnhealthy = "unhealthy" -) - type NginxPlusScraper struct { - previousServerZoneResponses map[string]ResponseStatuses - previousLocationZoneResponses map[string]ResponseStatuses - previousServerZoneRequests map[string]int64 - previousLocationZoneRequests map[string]int64 - plusClient *plusapi.NginxClient - cfg *Config - mb *metadata.MetricsBuilder - rb *metadata.ResourceBuilder - logger *zap.Logger - settings receiver.Settings - init sync.Once - previousHTTPRequestsTotal uint64 -} - -type ResponseStatuses struct { - oneHundredStatusRange int64 - twoHundredStatusRange int64 - threeHundredStatusRange int64 - fourHundredStatusRange int64 - fiveHundredStatusRange int64 + locationZoneMetrics *record.LocationZoneMetrics + serverZoneMetrics *record.ServerZoneMetrics + httpMetrics *record.HTTPMetrics + plusClient *plusapi.NginxClient + cfg *Config + mb *metadata.MetricsBuilder + rb *metadata.ResourceBuilder + logger *zap.Logger + settings receiver.Settings + init sync.Once } func newNginxPlusScraper( @@ -136,11 +117,9 @@ func (nps *NginxPlusScraper) Scrape(ctx context.Context) (pmetric.Metrics, error return } - nps.previousHTTPRequestsTotal = stats.HTTPRequests.Total - nps.createPreviousServerZoneResponses(stats) - nps.createPreviousLocationZoneResponses(stats) - nps.createPreviousServerZoneRequests(stats) - nps.createPreviousLocationZoneRequests(stats) + nps.httpMetrics = record.NewHTTPMetrics(stats, nps.mb) + nps.locationZoneMetrics = record.NewLocationZoneMetrics(stats, nps.mb) + nps.serverZoneMetrics = record.NewServerZoneMetrics(stats, nps.mb) }) stats, err := nps.plusClient.GetStats(ctx) @@ -162,1083 +141,25 @@ func (nps *NginxPlusScraper) Shutdown(ctx context.Context) error { return nil } -func (nps *NginxPlusScraper) createPreviousLocationZoneResponses(stats *plusapi.Stats) { - previousLocationZoneResponses := make(map[string]ResponseStatuses) - for lzName, lz := range stats.LocationZones { - respStatus := ResponseStatuses{ - oneHundredStatusRange: int64(lz.Responses.Responses1xx), - twoHundredStatusRange: int64(lz.Responses.Responses2xx), - threeHundredStatusRange: int64(lz.Responses.Responses3xx), - fourHundredStatusRange: int64(lz.Responses.Responses4xx), - fiveHundredStatusRange: int64(lz.Responses.Responses5xx), - } - - previousLocationZoneResponses[lzName] = respStatus - } - - nps.previousLocationZoneResponses = previousLocationZoneResponses -} - -func (nps *NginxPlusScraper) createPreviousServerZoneRequests(stats *plusapi.Stats) { - previousServerZoneRequests := make(map[string]int64) - for szName, sz := range stats.ServerZones { - previousServerZoneRequests[szName] = int64(sz.Requests) - } - nps.previousServerZoneRequests = previousServerZoneRequests -} - -func (nps *NginxPlusScraper) createPreviousLocationZoneRequests(stats *plusapi.Stats) { - previousLocationZoneRequests := make(map[string]int64) - for lzName, lz := range stats.LocationZones { - previousLocationZoneRequests[lzName] = lz.Requests - } - nps.previousLocationZoneRequests = previousLocationZoneRequests -} - -func (nps *NginxPlusScraper) createPreviousServerZoneResponses(stats *plusapi.Stats) { - previousServerZoneResponses := make(map[string]ResponseStatuses) - for szName, sz := range stats.ServerZones { - respStatus := ResponseStatuses{ - oneHundredStatusRange: int64(sz.Responses.Responses1xx), - twoHundredStatusRange: int64(sz.Responses.Responses2xx), - threeHundredStatusRange: int64(sz.Responses.Responses3xx), - fourHundredStatusRange: int64(sz.Responses.Responses4xx), - fiveHundredStatusRange: int64(sz.Responses.Responses5xx), - } - - previousServerZoneResponses[szName] = respStatus - } - - nps.previousServerZoneResponses = previousServerZoneResponses -} - func (nps *NginxPlusScraper) recordMetrics(stats *plusapi.Stats) { now := pcommon.NewTimestampFromTime(time.Now()) // NGINX config reloads nps.mb.RecordNginxConfigReloadsDataPoint(now, int64(stats.NginxInfo.Generation)) - // Connections - nps.mb.RecordNginxHTTPConnectionsDataPoint( - now, - int64(stats.Connections.Accepted), - metadata.AttributeNginxConnectionsOutcomeACCEPTED, - ) - nps.mb.RecordNginxHTTPConnectionsDataPoint( - now, - int64(stats.Connections.Dropped), - metadata.AttributeNginxConnectionsOutcomeDROPPED, - ) - nps.mb.RecordNginxHTTPConnectionCountDataPoint( - now, - int64(stats.Connections.Active), - metadata.AttributeNginxConnectionsOutcomeACTIVE, - ) - nps.mb.RecordNginxHTTPConnectionCountDataPoint( - now, - int64(stats.Connections.Idle), - metadata.AttributeNginxConnectionsOutcomeIDLE, - ) - - // HTTP Requests - nps.mb.RecordNginxHTTPRequestsDataPoint(now, int64(stats.HTTPRequests.Total), "", 0) - - requestsDiff := int64(stats.HTTPRequests.Total) - int64(nps.previousHTTPRequestsTotal) - nps.mb.RecordNginxHTTPRequestCountDataPoint(now, requestsDiff, "", 0) - nps.previousHTTPRequestsTotal = stats.HTTPRequests.Total - - nps.recordCacheMetrics(stats, now) - nps.recordHTTPLimitMetrics(stats, now) - nps.recordLocationZoneMetrics(stats, now) - nps.recordServerZoneMetrics(stats, now) - nps.recordHTTPUpstreamPeerMetrics(stats, now) - nps.recordSlabPageMetrics(stats, now) - nps.recordSSLMetrics(now, stats) - nps.recordStreamMetrics(stats, now) -} - -func (nps *NginxPlusScraper) recordStreamMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for name, streamServerZone := range stats.StreamServerZones { - nps.mb.RecordNginxStreamIoDataPoint( - now, - int64(streamServerZone.Received), - metadata.AttributeNginxIoDirectionReceive, - name, - ) - nps.mb.RecordNginxStreamIoDataPoint( - now, - int64(streamServerZone.Sent), - metadata.AttributeNginxIoDirectionTransmit, - name, - ) - nps.mb.RecordNginxStreamConnectionAcceptedDataPoint(now, int64(streamServerZone.Connections), name) - nps.mb.RecordNginxStreamConnectionDiscardedDataPoint(now, int64(streamServerZone.Discarded), name) - nps.mb.RecordNginxStreamConnectionProcessingCountDataPoint(now, int64(streamServerZone.Processing), name) - nps.mb.RecordNginxStreamSessionStatusDataPoint( - now, - int64(streamServerZone.Sessions.Sessions2xx), - metadata.AttributeNginxStatusRange2xx, - name, - ) - nps.mb.RecordNginxStreamSessionStatusDataPoint( - now, - int64(streamServerZone.Sessions.Sessions4xx), - metadata.AttributeNginxStatusRange4xx, - name, - ) - nps.mb.RecordNginxStreamSessionStatusDataPoint( - now, - int64(streamServerZone.Sessions.Sessions5xx), - metadata.AttributeNginxStatusRange5xx, - name, - ) - nps.mb.RecordNginxStreamSessionStatusDataPoint(now, int64(streamServerZone.Sessions.Total), 0, name) - } - - for upstreamName, upstream := range stats.StreamUpstreams { - peerStates := make(map[string]int) - - for _, peer := range upstream.Peers { - nps.mb.RecordNginxStreamUpstreamPeerIoDataPoint( - now, - int64(peer.Received), - metadata.AttributeNginxIoDirectionReceive, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerIoDataPoint( - now, - int64(peer.Sent), - metadata.AttributeNginxIoDirectionTransmit, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerConnectionCountDataPoint( - now, - int64(peer.Active), - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerConnectionTimeDataPoint( - now, - int64(peer.ConnectTime), - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxStreamUpstreamPeerConnectionsDataPoint( - now, - int64(peer.Connections), - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxStreamUpstreamPeerHealthChecksDataPoint( - now, - int64(peer.HealthChecks.Checks), - 0, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerHealthChecksDataPoint( - now, - int64(peer.HealthChecks.Fails), - metadata.AttributeNginxHealthCheckFAIL, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerHealthChecksDataPoint( - now, - int64(peer.HealthChecks.Unhealthy), - metadata.AttributeNginxHealthCheckUNHEALTHY, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxStreamUpstreamPeerResponseTimeDataPoint( - now, - int64(peer.ResponseTime), - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerTtfbTimeDataPoint( - now, - int64(peer.FirstByteTime), - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerUnavailablesDataPoint( - now, - int64(peer.Unavail), - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxStreamUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateChecking), - metadata.AttributeNginxPeerStateCHECKING, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateDown), - metadata.AttributeNginxPeerStateDOWN, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateDraining), - metadata.AttributeNginxPeerStateDRAINING, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateUnavail), - metadata.AttributeNginxPeerStateUNAVAILABLE, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateUnhealthy), - metadata.AttributeNginxPeerStateUNHEALTHY, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxStreamUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateUp), - metadata.AttributeNginxPeerStateUP, - upstream.Zone, - upstreamName, - peer.Server, - peer.Name, - ) - - peerStates[peer.State]++ - } - - nps.mb.RecordNginxStreamUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateChecking]), - metadata.AttributeNginxPeerStateCHECKING, - upstream.Zone, - upstreamName, - ) - nps.mb.RecordNginxStreamUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateDown]), - metadata.AttributeNginxPeerStateDOWN, - upstream.Zone, - upstreamName, - ) - nps.mb.RecordNginxStreamUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateDraining]), - metadata.AttributeNginxPeerStateDRAINING, - upstream.Zone, - upstreamName, - ) - nps.mb.RecordNginxStreamUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateUnavail]), - metadata.AttributeNginxPeerStateUNAVAILABLE, - upstream.Zone, - upstreamName, - ) - nps.mb.RecordNginxStreamUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateUnhealthy]), - metadata.AttributeNginxPeerStateUNHEALTHY, - upstream.Zone, - upstreamName, - ) - nps.mb.RecordNginxStreamUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateUp]), - metadata.AttributeNginxPeerStateUP, - upstream.Zone, - upstreamName, - ) - - nps.mb.RecordNginxStreamUpstreamZombieCountDataPoint(now, int64(upstream.Zombies), upstream.Zone, upstreamName) - } -} - -func (nps *NginxPlusScraper) recordSSLMetrics(now pcommon.Timestamp, stats *plusapi.Stats) { - nps.mb.RecordNginxSslHandshakesDataPoint( - now, - int64(stats.SSL.HandshakesFailed), - metadata.AttributeNginxSslStatusFAILED, - 0, - ) - nps.mb.RecordNginxSslHandshakesDataPoint(now, int64(stats.SSL.Handshakes), 0, 0) - nps.mb.RecordNginxSslHandshakesDataPoint( - now, - int64(stats.SSL.SessionReuses), - metadata.AttributeNginxSslStatusREUSE, - 0, - ) - nps.mb.RecordNginxSslHandshakesDataPoint( - now, - int64(stats.SSL.NoCommonProtocol), - metadata.AttributeNginxSslStatusFAILED, - metadata.AttributeNginxSslHandshakeReasonNOCOMMONPROTOCOL, - ) - nps.mb.RecordNginxSslHandshakesDataPoint( - now, - int64(stats.SSL.NoCommonCipher), - metadata.AttributeNginxSslStatusFAILED, - metadata.AttributeNginxSslHandshakeReasonNOCOMMONCIPHER, - ) - nps.mb.RecordNginxSslHandshakesDataPoint( - now, - int64(stats.SSL.HandshakeTimeout), - metadata.AttributeNginxSslStatusFAILED, - metadata.AttributeNginxSslHandshakeReasonTIMEOUT, - ) - nps.mb.RecordNginxSslHandshakesDataPoint( - now, - int64(stats.SSL.PeerRejectedCert), - metadata.AttributeNginxSslStatusFAILED, - metadata.AttributeNginxSslHandshakeReasonCERTREJECTED, - ) - - nps.mb.RecordNginxSslCertificateVerifyFailuresDataPoint( - now, - int64(stats.SSL.VerifyFailures.NoCert), - metadata.AttributeNginxSslVerifyFailureReasonNOCERT, - ) - nps.mb.RecordNginxSslCertificateVerifyFailuresDataPoint( - now, - int64(stats.SSL.VerifyFailures.ExpiredCert), - metadata.AttributeNginxSslVerifyFailureReasonEXPIREDCERT, - ) - nps.mb.RecordNginxSslCertificateVerifyFailuresDataPoint( - now, - int64(stats.SSL.VerifyFailures.RevokedCert), - metadata.AttributeNginxSslVerifyFailureReasonREVOKEDCERT, - ) - nps.mb.RecordNginxSslCertificateVerifyFailuresDataPoint( - now, - int64(stats.SSL.VerifyFailures.HostnameMismatch), - metadata.AttributeNginxSslVerifyFailureReasonHOSTNAMEMISMATCH, - ) - nps.mb.RecordNginxSslCertificateVerifyFailuresDataPoint( - now, - int64(stats.SSL.VerifyFailures.Other), - metadata.AttributeNginxSslVerifyFailureReasonOTHER, - ) -} - -func (nps *NginxPlusScraper) recordSlabPageMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for name, slab := range stats.Slabs { - nps.mb.RecordNginxSlabPageFreeDataPoint(now, int64(slab.Pages.Free), name) - nps.mb.RecordNginxSlabPageUsageDataPoint(now, int64(slab.Pages.Used), name) - - for slotName, slot := range slab.Slots { - slotNumber, err := strconv.ParseInt(slotName, 10, 64) - if err != nil { - nps.logger.Warn("Invalid slot name for NGINX Plus slab metrics", zap.Error(err)) - } - - nps.mb.RecordNginxSlabSlotUsageDataPoint(now, int64(slot.Used), slotNumber, name) - nps.mb.RecordNginxSlabSlotFreeDataPoint(now, int64(slot.Free), slotNumber, name) - nps.mb.RecordNginxSlabSlotAllocationsDataPoint( - now, - int64(slot.Fails), - slotNumber, - metadata.AttributeNginxSlabSlotAllocationResultFAILURE, - name, - ) - nps.mb.RecordNginxSlabSlotAllocationsDataPoint( - now, - int64(slot.Reqs), - slotNumber, - metadata.AttributeNginxSlabSlotAllocationResultSUCCESS, - name, - ) - } - } -} - -func (nps *NginxPlusScraper) recordHTTPUpstreamPeerMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for name, upstream := range stats.Upstreams { - nps.mb.RecordNginxHTTPUpstreamKeepaliveCountDataPoint(now, int64(upstream.Keepalive), upstream.Zone, name) - - peerStates := make(map[string]int) - - for _, peer := range upstream.Peers { - nps.mb.RecordNginxHTTPUpstreamPeerIoDataPoint( - now, - int64(peer.Received), - metadata.AttributeNginxIoDirectionReceive, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerIoDataPoint( - now, - int64(peer.Sent), - metadata.AttributeNginxIoDirectionTransmit, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxHTTPUpstreamPeerConnectionCountDataPoint( - now, - int64(peer.Active), - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxHTTPUpstreamPeerFailsDataPoint( - now, - int64(peer.Fails), - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerHeaderTimeDataPoint( - now, - int64(peer.HeaderTime), - upstream.Zone, - name, - peer.Server, - peer.Name, - ) + nps.httpMetrics.RecordHTTPMetrics(stats, now) + nps.httpMetrics.RecordHTTPLimitMetrics(stats, now) - nps.mb.RecordNginxHTTPUpstreamPeerHealthChecksDataPoint( - now, - int64(peer.HealthChecks.Checks), - 0, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerHealthChecksDataPoint( - now, - int64(peer.HealthChecks.Fails), - metadata.AttributeNginxHealthCheckFAIL, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerHealthChecksDataPoint( - now, - int64(peer.HealthChecks.Unhealthy), - metadata.AttributeNginxHealthCheckUNHEALTHY, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) + nps.locationZoneMetrics.RecordLocationZoneMetrics(stats, now) + nps.serverZoneMetrics.RecordServerZoneMetrics(stats, now) - nps.mb.RecordNginxHTTPUpstreamPeerRequestsDataPoint( - now, - int64(peer.Requests), - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerResponseTimeDataPoint( - now, - int64(peer.ResponseTime), - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( - now, - int64(peer.Responses.Total), - 0, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) + record.RecordCacheMetrics(nps.mb, stats, now) - nps.mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( - now, - int64(peer.Responses.Responses1xx), - metadata.AttributeNginxStatusRange1xx, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( - now, - int64(peer.Responses.Responses2xx), - metadata.AttributeNginxStatusRange2xx, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( - now, - int64(peer.Responses.Responses3xx), - metadata.AttributeNginxStatusRange3xx, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( - now, - int64(peer.Responses.Responses4xx), - metadata.AttributeNginxStatusRange4xx, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerResponsesDataPoint( - now, - int64(peer.Responses.Responses5xx), - metadata.AttributeNginxStatusRange5xx, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) + record.RecordHTTPUpstreamPeerMetrics(nps.mb, stats, now) + record.RecordStreamMetrics(nps.mb, stats, now) - nps.mb.RecordNginxHTTPUpstreamPeerUnavailablesDataPoint( - now, - int64(peer.Unavail), - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - - nps.mb.RecordNginxHTTPUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateChecking), - metadata.AttributeNginxPeerStateCHECKING, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateDown), - metadata.AttributeNginxPeerStateDOWN, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateDraining), - metadata.AttributeNginxPeerStateDRAINING, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateUnavail), - metadata.AttributeNginxPeerStateUNAVAILABLE, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateUnhealthy), - metadata.AttributeNginxPeerStateUNHEALTHY, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerStateDataPoint( - now, - boolToInt64(peer.State == peerStateUp), - metadata.AttributeNginxPeerStateUP, - upstream.Zone, - name, - peer.Server, - peer.Name, - ) - - peerStates[peer.State]++ - } - - nps.mb.RecordNginxHTTPUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateChecking]), - metadata.AttributeNginxPeerStateCHECKING, - upstream.Zone, - name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateDown]), - metadata.AttributeNginxPeerStateDOWN, - upstream.Zone, - name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateDraining]), - metadata.AttributeNginxPeerStateDRAINING, - upstream.Zone, - name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateUnavail]), - metadata.AttributeNginxPeerStateUNAVAILABLE, - upstream.Zone, - name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateUnhealthy]), - metadata.AttributeNginxPeerStateUNHEALTHY, - upstream.Zone, - name, - ) - nps.mb.RecordNginxHTTPUpstreamPeerCountDataPoint( - now, - int64(peerStates[peerStateUp]), - metadata.AttributeNginxPeerStateUP, - upstream.Zone, - name, - ) - - nps.mb.RecordNginxHTTPUpstreamQueueLimitDataPoint(now, int64(upstream.Queue.MaxSize), upstream.Zone, name) - nps.mb.RecordNginxHTTPUpstreamQueueOverflowsDataPoint(now, int64(upstream.Queue.Overflows), upstream.Zone, name) - nps.mb.RecordNginxHTTPUpstreamQueueUsageDataPoint(now, int64(upstream.Queue.Size), upstream.Zone, name) - nps.mb.RecordNginxHTTPUpstreamZombieCountDataPoint(now, int64(upstream.Zombies), upstream.Zone, name) - } -} - -func (nps *NginxPlusScraper) recordServerZoneMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for szName, sz := range stats.ServerZones { - nps.mb.RecordNginxHTTPRequestIoDataPoint( - now, - int64(sz.Received), - metadata.AttributeNginxIoDirectionReceive, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - nps.mb.RecordNginxHTTPRequestIoDataPoint( - now, - int64(sz.Sent), - metadata.AttributeNginxIoDirectionTransmit, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - - nps.mb.RecordNginxHTTPRequestsDataPoint(now, int64(sz.Requests), szName, metadata.AttributeNginxZoneTypeSERVER) - - nps.mb.RecordNginxHTTPRequestCountDataPoint(now, - int64(sz.Requests)-nps.previousServerZoneRequests[szName], - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - nps.previousServerZoneRequests[szName] = int64(sz.Requests) - - nps.recordServerZoneHTTPMetrics(sz, szName, now) - - nps.mb.RecordNginxHTTPRequestDiscardedDataPoint(now, int64(sz.Discarded), - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - - nps.mb.RecordNginxHTTPRequestProcessingCountDataPoint(now, int64(sz.Processing), - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - } -} - -//nolint:dupl // Duplicate of recordLocationZoneHTTPMetrics but same function can not be used due to plusapi.ServerZone -func (nps *NginxPlusScraper) recordServerZoneHTTPMetrics(sz plusapi.ServerZone, szName string, now pcommon.Timestamp) { - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses1xx), - metadata.AttributeNginxStatusRange1xx, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses2xx), - metadata.AttributeNginxStatusRange2xx, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses3xx), - metadata.AttributeNginxStatusRange3xx, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses4xx), - metadata.AttributeNginxStatusRange4xx, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(sz.Responses.Responses5xx), - metadata.AttributeNginxStatusRange5xx, - szName, - metadata.AttributeNginxZoneTypeSERVER, - ) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(sz.Responses.Responses1xx)-nps.previousServerZoneResponses[szName].oneHundredStatusRange, - metadata.AttributeNginxStatusRange1xx, - szName, - metadata.AttributeNginxZoneTypeSERVER) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(sz.Responses.Responses2xx)-nps.previousServerZoneResponses[szName].twoHundredStatusRange, - metadata.AttributeNginxStatusRange2xx, - szName, - metadata.AttributeNginxZoneTypeSERVER) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(sz.Responses.Responses3xx)-nps.previousServerZoneResponses[szName].threeHundredStatusRange, - metadata.AttributeNginxStatusRange3xx, - szName, - metadata.AttributeNginxZoneTypeSERVER) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(sz.Responses.Responses4xx)-nps.previousServerZoneResponses[szName].fourHundredStatusRange, - metadata.AttributeNginxStatusRange4xx, - szName, - metadata.AttributeNginxZoneTypeSERVER) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(sz.Responses.Responses5xx)-nps.previousServerZoneResponses[szName].fiveHundredStatusRange, - metadata.AttributeNginxStatusRange5xx, - szName, - metadata.AttributeNginxZoneTypeSERVER) - - respStatus := ResponseStatuses{ - oneHundredStatusRange: int64(sz.Responses.Responses1xx), - twoHundredStatusRange: int64(sz.Responses.Responses2xx), - threeHundredStatusRange: int64(sz.Responses.Responses3xx), - fourHundredStatusRange: int64(sz.Responses.Responses4xx), - fiveHundredStatusRange: int64(sz.Responses.Responses5xx), - } - - nps.previousServerZoneResponses[szName] = respStatus -} - -func (nps *NginxPlusScraper) recordLocationZoneMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for lzName, lz := range stats.LocationZones { - nps.mb.RecordNginxHTTPRequestIoDataPoint( - now, - lz.Received, - metadata.AttributeNginxIoDirectionReceive, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - nps.mb.RecordNginxHTTPRequestIoDataPoint( - now, - lz.Sent, - metadata.AttributeNginxIoDirectionTransmit, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - - nps.mb.RecordNginxHTTPRequestsDataPoint( - now, - lz.Requests, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - - nps.mb.RecordNginxHTTPRequestCountDataPoint(now, - lz.Requests-nps.previousLocationZoneRequests[lzName], - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - - nps.previousLocationZoneRequests[lzName] = lz.Requests - - nps.recordLocationZoneHTTPMetrics(lz, lzName, now) - - nps.mb.RecordNginxHTTPRequestDiscardedDataPoint(now, lz.Discarded, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - } -} - -//nolint:dupl // Duplicate of recordServerZoneHTTPMetrics but same function can not be used due to plusapi.LocationZone -func (nps *NginxPlusScraper) recordLocationZoneHTTPMetrics(lz plusapi.LocationZone, - lzName string, now pcommon.Timestamp, -) { - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses1xx), - metadata.AttributeNginxStatusRange1xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses2xx), - metadata.AttributeNginxStatusRange2xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses3xx), - metadata.AttributeNginxStatusRange3xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses4xx), - metadata.AttributeNginxStatusRange4xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - - nps.mb.RecordNginxHTTPResponseStatusDataPoint(now, int64(lz.Responses.Responses5xx), - metadata.AttributeNginxStatusRange5xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION, - ) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(lz.Responses.Responses1xx)-nps.previousLocationZoneResponses[lzName].oneHundredStatusRange, - metadata.AttributeNginxStatusRange1xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(lz.Responses.Responses2xx)-nps.previousLocationZoneResponses[lzName].twoHundredStatusRange, - metadata.AttributeNginxStatusRange2xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(lz.Responses.Responses3xx)-nps.previousLocationZoneResponses[lzName].threeHundredStatusRange, - metadata.AttributeNginxStatusRange3xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(lz.Responses.Responses4xx)-nps.previousLocationZoneResponses[lzName].fourHundredStatusRange, - metadata.AttributeNginxStatusRange4xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION) - - nps.mb.RecordNginxHTTPResponseCountDataPoint(now, - int64(lz.Responses.Responses5xx)-nps.previousLocationZoneResponses[lzName].fiveHundredStatusRange, - metadata.AttributeNginxStatusRange5xx, - lzName, - metadata.AttributeNginxZoneTypeLOCATION) - - respStatus := ResponseStatuses{ - oneHundredStatusRange: int64(lz.Responses.Responses1xx), - twoHundredStatusRange: int64(lz.Responses.Responses2xx), - threeHundredStatusRange: int64(lz.Responses.Responses3xx), - fourHundredStatusRange: int64(lz.Responses.Responses4xx), - fiveHundredStatusRange: int64(lz.Responses.Responses5xx), - } - - nps.previousLocationZoneResponses[lzName] = respStatus -} - -func (nps *NginxPlusScraper) recordHTTPLimitMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for name, limitConnection := range stats.HTTPLimitConnections { - nps.mb.RecordNginxHTTPLimitConnRequestsDataPoint( - now, - int64(limitConnection.Passed), - metadata.AttributeNginxLimitConnOutcomePASSED, - name, - ) - nps.mb.RecordNginxHTTPLimitConnRequestsDataPoint( - now, - int64(limitConnection.Rejected), - metadata.AttributeNginxLimitConnOutcomeREJECTED, - name, - ) - nps.mb.RecordNginxHTTPLimitConnRequestsDataPoint( - now, - int64(limitConnection.RejectedDryRun), - metadata.AttributeNginxLimitConnOutcomeREJECTEDDRYRUN, - name, - ) - } - - for name, limitRequest := range stats.HTTPLimitRequests { - nps.mb.RecordNginxHTTPLimitReqRequestsDataPoint( - now, - int64(limitRequest.Passed), - metadata.AttributeNginxLimitReqOutcomePASSED, - name, - ) - nps.mb.RecordNginxHTTPLimitReqRequestsDataPoint( - now, - int64(limitRequest.Rejected), - metadata.AttributeNginxLimitReqOutcomeREJECTED, - name, - ) - nps.mb.RecordNginxHTTPLimitReqRequestsDataPoint( - now, - int64(limitRequest.RejectedDryRun), - metadata.AttributeNginxLimitReqOutcomeREJECTEDDRYRUN, - name, - ) - nps.mb.RecordNginxHTTPLimitReqRequestsDataPoint( - now, - int64(limitRequest.Delayed), - metadata.AttributeNginxLimitReqOutcomeDELAYED, - name, - ) - nps.mb.RecordNginxHTTPLimitReqRequestsDataPoint( - now, - int64(limitRequest.DelayedDryRun), - metadata.AttributeNginxLimitReqOutcomeDELAYEDDRYRUN, - name, - ) - } -} - -func (nps *NginxPlusScraper) recordCacheMetrics(stats *plusapi.Stats, now pcommon.Timestamp) { - for name, cache := range stats.Caches { - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Bypass.Bytes), - metadata.AttributeNginxCacheOutcomeBYPASS, - name, - ) - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Expired.Bytes), - metadata.AttributeNginxCacheOutcomeEXPIRED, - name, - ) - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Hit.Bytes), - metadata.AttributeNginxCacheOutcomeHIT, - name, - ) - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Miss.Bytes), - metadata.AttributeNginxCacheOutcomeMISS, - name, - ) - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Revalidated.Bytes), - metadata.AttributeNginxCacheOutcomeREVALIDATED, - name, - ) - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Stale.Bytes), - metadata.AttributeNginxCacheOutcomeSTALE, - name, - ) - nps.mb.RecordNginxCacheBytesReadDataPoint( - now, - int64(cache.Updating.Bytes), - metadata.AttributeNginxCacheOutcomeUPDATING, - name, - ) - - nps.mb.RecordNginxCacheMemoryLimitDataPoint(now, int64(cache.MaxSize), name) - nps.mb.RecordNginxCacheMemoryUsageDataPoint(now, int64(cache.Size), name) - - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Bypass.Responses), - metadata.AttributeNginxCacheOutcomeBYPASS, - name, - ) - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Expired.Responses), - metadata.AttributeNginxCacheOutcomeEXPIRED, - name, - ) - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Hit.Responses), - metadata.AttributeNginxCacheOutcomeHIT, - name, - ) - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Miss.Responses), - metadata.AttributeNginxCacheOutcomeMISS, - name, - ) - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Revalidated.Responses), - metadata.AttributeNginxCacheOutcomeREVALIDATED, - name, - ) - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Stale.Responses), - metadata.AttributeNginxCacheOutcomeSTALE, - name, - ) - nps.mb.RecordNginxCacheResponsesDataPoint( - now, - int64(cache.Updating.Responses), - metadata.AttributeNginxCacheOutcomeUPDATING, - name, - ) - } + record.RecordSlabPageMetrics(nps.mb, stats, now, nps.logger) + record.RecordSSLMetrics(nps.mb, now, stats) } func socketClient(ctx context.Context, socketPath string) *http.Client { @@ -1251,12 +172,3 @@ func socketClient(ctx context.Context, socketPath string) *http.Client { }, } } - -//nolint:revive // booleanValue flag is mandatory -func boolToInt64(booleanValue bool) int64 { - if booleanValue { - return 1 - } - - return 0 -} diff --git a/internal/collector/nginxplusreceiver/scraper_test.go b/internal/collector/nginxplusreceiver/scraper_test.go index 954a0ad8e..eb4abc9fa 100644 --- a/internal/collector/nginxplusreceiver/scraper_test.go +++ b/internal/collector/nginxplusreceiver/scraper_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "testing" + "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver/record" "go.opentelemetry.io/collector/component/componenttest" "github.com/nginx/agent/v3/test/helpers" @@ -38,35 +39,35 @@ func TestScraper(t *testing.T) { // To test the nginx.http.response.count metric calculation we need to set the previousLocationZoneResponses & // previousSeverZoneResponses then call scrape a second time as the first time it is called the previous responses // are set using the API - scraper.previousLocationZoneResponses = map[string]ResponseStatuses{ + scraper.locationZoneMetrics.PreviousLocationZoneResponses = map[string]record.ResponseStatuses{ "location_test": { - oneHundredStatusRange: 3, // 4 - twoHundredStatusRange: 29, // 2 - threeHundredStatusRange: 0, - fourHundredStatusRange: 1, // 2 - fiveHundredStatusRange: 0, + OneHundredStatusRange: 3, // 4 + TwoHundredStatusRange: 29, // 2 + ThreeHundredStatusRange: 0, + FourHundredStatusRange: 1, // 2 + FiveHundredStatusRange: 0, }, } - scraper.previousServerZoneResponses = map[string]ResponseStatuses{ + scraper.serverZoneMetrics.PreviousServerZoneResponses = map[string]record.ResponseStatuses{ "test": { - oneHundredStatusRange: 3, // 2 - twoHundredStatusRange: 0, // 29 - threeHundredStatusRange: 0, - fourHundredStatusRange: 1, // 1 - fiveHundredStatusRange: 0, + OneHundredStatusRange: 3, // 2 + TwoHundredStatusRange: 0, // 29 + ThreeHundredStatusRange: 0, + FourHundredStatusRange: 1, // 1 + FiveHundredStatusRange: 0, }, } - scraper.previousLocationZoneRequests = map[string]int64{ + scraper.locationZoneMetrics.PreviousLocationZoneRequests = map[string]int64{ "location_test": 30, // 5 } - scraper.previousServerZoneRequests = map[string]int64{ + scraper.serverZoneMetrics.PreviousServerZoneRequests = map[string]int64{ "test": 29, // 3 } - scraper.previousHTTPRequestsTotal = 3 + scraper.httpMetrics.PreviousHTTPRequestsTotal = 3 actualMetrics, err := scraper.Scrape(context.Background()) require.NoError(t, err) From 06189cabae3fef6a82752de633736f98c58cc099 Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Tue, 16 Sep 2025 17:04:53 +0100 Subject: [PATCH 12/13] add tests --- internal/collector/nginxplusreceiver/scraper_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/collector/nginxplusreceiver/scraper_test.go b/internal/collector/nginxplusreceiver/scraper_test.go index eb4abc9fa..7e6abd0ec 100644 --- a/internal/collector/nginxplusreceiver/scraper_test.go +++ b/internal/collector/nginxplusreceiver/scraper_test.go @@ -29,6 +29,15 @@ func TestScraper(t *testing.T) { assert.True(t, ok) cfg.APIDetails.URL = nginxPlusMock.URL + "/api" + tmpDir := t.TempDir() + _, cert := helpers.GenerateSelfSignedCert(t) + + caContents := helpers.Cert{Name: "ca.pem", Type: "CERTIFICATE", Contents: cert} + caFile := helpers.WriteCertFiles(t, tmpDir, caContents) + t.Logf("Ca File: %s", caFile) + + cfg.APIDetails.Ca = caFile + scraper := newNginxPlusScraper(receivertest.NewNopSettings(component.Type{}), cfg) err := scraper.Start(context.Background(), componenttest.NewNopHost()) require.NoError(t, err) From 4c1a391129fe6758cffe2b801dadfa3bacb084db Mon Sep 17 00:00:00 2001 From: Aphral Griffin Date: Tue, 16 Sep 2025 17:09:32 +0100 Subject: [PATCH 13/13] add tests --- internal/collector/nginxplusreceiver/scraper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/collector/nginxplusreceiver/scraper_test.go b/internal/collector/nginxplusreceiver/scraper_test.go index 7e6abd0ec..d55a5e160 100644 --- a/internal/collector/nginxplusreceiver/scraper_test.go +++ b/internal/collector/nginxplusreceiver/scraper_test.go @@ -35,7 +35,7 @@ func TestScraper(t *testing.T) { caContents := helpers.Cert{Name: "ca.pem", Type: "CERTIFICATE", Contents: cert} caFile := helpers.WriteCertFiles(t, tmpDir, caContents) t.Logf("Ca File: %s", caFile) - + cfg.APIDetails.Ca = caFile scraper := newNginxPlusScraper(receivertest.NewNopSettings(component.Type{}), cfg)