diff --git a/changelog/fragments/1774128388-suppress-health-degradation.yaml b/changelog/fragments/1774128388-suppress-health-degradation.yaml new file mode 100644 index 000000000000..3d5184bb9a9b --- /dev/null +++ b/changelog/fragments/1774128388-suppress-health-degradation.yaml @@ -0,0 +1,7 @@ +kind: feature + +summary: Add status_reporting stream config to control which health states propagate to unit aggregate health + +component: libbeat + +pr: https://github.com/elastic/beats/pull/49511 diff --git a/x-pack/libbeat/management/unit.go b/x-pack/libbeat/management/unit.go index 729be62c77a7..0e9d62effdb5 100644 --- a/x-pack/libbeat/management/unit.go +++ b/x-pack/libbeat/management/unit.go @@ -8,15 +8,26 @@ import ( "fmt" "sync" + "google.golang.org/protobuf/types/known/structpb" + "github.com/elastic/beats/v7/libbeat/management/status" "github.com/elastic/elastic-agent-client/v7/pkg/client" "github.com/elastic/elastic-agent-libs/logp" ) +// streamStatusReporting controls which health states a stream contributes +// to the unit's aggregate health. Both fields default to true so that +// existing behaviour is preserved when the config block is absent. +type streamStatusReporting struct { + reportDegraded bool + reportFailed bool +} + // unitState is the current state of a unit type unitState struct { - state status.Status - msg string + state status.Status + msg string + statusReporting streamStatusReporting } type clientUnit interface { @@ -68,7 +79,7 @@ func getUnitState(s status.Status) client.UnitState { } } -// getUnitState converts status.Status to client.UnitState +// getStatus converts client.UnitState to status.Status func getStatus(s client.UnitState) status.Status { switch s { case client.UnitStateStarting: @@ -90,6 +101,30 @@ func getStatus(s client.UnitState) status.Status { } } +// parseStatusReporting extracts status_reporting from a protobuf Struct source, +// using fallback as the default when fields are absent. +func parseStatusReporting(src *structpb.Struct, fallback streamStatusReporting) streamStatusReporting { + if src == nil { + return fallback + } + srVal, ok := src.GetFields()["status_reporting"] + if !ok { + return fallback + } + srMap := srVal.GetStructValue() + if srMap == nil { + return fallback + } + sr := fallback + if v, ok := srMap.GetFields()["report_degraded"]; ok { + sr.reportDegraded = v.GetBoolValue() + } + if v, ok := srMap.GetFields()["report_failed"]; ok { + sr.reportFailed = v.GetBoolValue() + } + return sr +} + func getStreamStates(expected client.Expected) (map[string]unitState, []string) { expectedCfg := expected.Config @@ -97,13 +132,23 @@ func getStreamStates(expected client.Expected) (map[string]unitState, []string) return nil, nil } + // Read input-level status_reporting as a default for all streams. + inputDefault := parseStatusReporting( + expectedCfg.GetSource(), + streamStatusReporting{reportDegraded: true, reportFailed: true}, + ) + streamStates := make(map[string]unitState, len(expectedCfg.Streams)) streamIDs := make([]string, len(expectedCfg.Streams)) for idx, stream := range expectedCfg.Streams { + // Stream-level status_reporting overrides the input-level default. + sr := parseStatusReporting(stream.GetSource(), inputDefault) + streamState := unitState{ - state: status.Unknown, - msg: "", + state: status.Unknown, + msg: "", + statusReporting: sr, } if id := stream.GetId(); id != "" { @@ -216,17 +261,25 @@ func (u *agentUnit) calcState() (status.Status, string) { return u.inputLevelState.state, u.inputLevelState.msg } - // inputLevelState state is marked as running, check the stream states + // inputLevelState state is marked as running, check the stream states. + // Streams with status_reporting.report_degraded or report_failed set to + // false are excluded from the aggregate health for those specific states. reportedStatus := status.Running reportedMsg := "Healthy" for _, streamState := range u.streamStates { switch streamState.state { case status.Degraded: + if !streamState.statusReporting.reportDegraded { + continue + } if reportedStatus != status.Degraded { reportedStatus = status.Degraded reportedMsg = streamState.msg } case status.Failed: + if !streamState.statusReporting.reportFailed { + continue + } // return the first failed stream return streamState.state, streamState.msg } @@ -307,8 +360,9 @@ func (u *agentUnit) updateStateForStream(streamID string, state status.Status, m } u.streamStates[streamID] = unitState{ - state: state, - msg: msg, + state: state, + msg: msg, + statusReporting: u.streamStates[streamID].statusReporting, } state, msg = u.calcState() @@ -347,8 +401,16 @@ func (u *agentUnit) update(cu *client.Unit) { newStreamStates, newStreamIDs := getStreamStates(cu.Expected()) + reportingChanged := false for key, state := range newStreamStates { - if _, exists := u.streamStates[key]; exists { + if existing, exists := u.streamStates[key]; exists { + // Preserve current health state but update the status_reporting flags + // in case the stream config changed. + if existing.statusReporting != state.statusReporting { + reportingChanged = true + } + existing.statusReporting = state.statusReporting + u.streamStates[key] = existing continue } @@ -372,6 +434,20 @@ func (u *agentUnit) update(cu *client.Unit) { } } } + + // If any stream's status_reporting config changed, recompute and publish + // the aggregate unit health so the change takes effect immediately. + if reportingChanged { + state, msg := u.calcState() + streamsPayload := make(map[string]interface{}, len(u.streamStates)) + for id, streamState := range u.streamStates { + streamsPayload[id] = map[string]interface{}{ + "status": getUnitState(streamState.state).String(), + "error": streamState.msg, + } + } + _ = u.clientUnit.UpdateState(getUnitState(state), msg, map[string]interface{}{"streams": streamsPayload}) + } } func (u *agentUnit) markAsDeleted() { diff --git a/x-pack/libbeat/management/unit_test.go b/x-pack/libbeat/management/unit_test.go index 9684ff5e16e9..4002643ef4e6 100644 --- a/x-pack/libbeat/management/unit_test.go +++ b/x-pack/libbeat/management/unit_test.go @@ -7,6 +7,8 @@ package management import ( "testing" + "google.golang.org/protobuf/types/known/structpb" + "github.com/elastic/elastic-agent-client/v7/pkg/client" "github.com/elastic/elastic-agent-client/v7/pkg/proto" @@ -171,16 +173,535 @@ func TestUnitUpdate(t *testing.T) { } if c.unit.reportedState != c.expectedUnitStatus { - t.Errorf("expected unit status %s, got %s", c.expectedUnitStatus, aUnit.inputLevelState.state) + t.Errorf("expected unit status %s, got %s", c.expectedUnitStatus, c.unit.reportedState) + } + + if c.unit.reportedMsg != c.expectedUnitMsg { + t.Errorf("expected unit msg %q, got %q", c.expectedUnitMsg, c.unit.reportedMsg) + } + }) + } +} + +func TestUnitUpdateStatusReporting(t *testing.T) { + + type StatusUpdate struct { + status status.Status + msg string + } + + const ( + Healthy = "Healthy" + Failed = "Failed" + Degraded = "Degraded" + ) + + // status_reporting with both report_degraded and report_failed set to false + muteBothSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + "report_failed": false, + }, + }) + + // status_reporting with only report_degraded muted + muteDegradedSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + }, + }) + + // status_reporting with only report_failed muted + muteFailedSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_failed": false, + }, + }) + + // status_reporting with both explicitly true (same as not set) + explicitTrueSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": true, + "report_failed": true, + }, + }) + + newUnit := func(streams []*proto.Stream) *mockClientUnit { + return &mockClientUnit{ + expected: client.Expected{ + Config: &proto.UnitExpectedConfig{ + Id: "input-1", + Streams: streams, + }, + }, + } + } + + cases := []struct { + name string + unit *mockClientUnit + inputLevelStatus StatusUpdate + streamStates map[string]StatusUpdate + expectedUnitStatus client.UnitState + expectedUnitMsg string + }{ + { + name: "muted stream degraded does not affect unit health", + unit: newUnit([]*proto.Stream{ + {Id: "stream-required"}, + {Id: "stream-muted", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-required": {status.Running, Healthy}, + "stream-muted": {status.Degraded, Degraded}, + }, + expectedUnitStatus: client.UnitStateHealthy, + expectedUnitMsg: Healthy, + }, + { + name: "muted stream failed does not affect unit health", + unit: newUnit([]*proto.Stream{ + {Id: "stream-required"}, + {Id: "stream-muted", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-required": {status.Running, Healthy}, + "stream-muted": {status.Failed, Failed}, + }, + expectedUnitStatus: client.UnitStateHealthy, + expectedUnitMsg: Healthy, + }, + { + name: "required stream degraded still affects unit health even with muted streams", + unit: newUnit([]*proto.Stream{ + {Id: "stream-required"}, + {Id: "stream-muted", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-required": {status.Degraded, Degraded}, + "stream-muted": {status.Degraded, Degraded}, + }, + expectedUnitStatus: client.UnitStateDegraded, + expectedUnitMsg: Degraded, + }, + { + name: "required stream failed still affects unit health even with muted streams", + unit: newUnit([]*proto.Stream{ + {Id: "stream-required"}, + {Id: "stream-muted", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-required": {status.Failed, Failed}, + "stream-muted": {status.Running, Healthy}, + }, + expectedUnitStatus: client.UnitStateFailed, + expectedUnitMsg: Failed, + }, + { + name: "all muted streams degraded and failed keeps unit healthy", + unit: newUnit([]*proto.Stream{ + {Id: "stream-muted-1", Source: muteBothSource}, + {Id: "stream-muted-2", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-muted-1": {status.Degraded, Degraded}, + "stream-muted-2": {status.Failed, Failed}, + }, + expectedUnitStatus: client.UnitStateHealthy, + expectedUnitMsg: Healthy, + }, + { + name: "explicit true behaves same as not set", + unit: newUnit([]*proto.Stream{ + {Id: "stream-explicit-true", Source: explicitTrueSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-explicit-true": {status.Degraded, Degraded}, + }, + expectedUnitStatus: client.UnitStateDegraded, + expectedUnitMsg: Degraded, + }, + { + name: "input level degraded is not affected by status_reporting", + unit: newUnit([]*proto.Stream{ + {Id: "stream-muted", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Degraded, Degraded}, + streamStates: map[string]StatusUpdate{ + "stream-muted": {status.Running, Healthy}, + }, + expectedUnitStatus: client.UnitStateDegraded, + expectedUnitMsg: Degraded, + }, + { + name: "input level failed is not affected by status_reporting", + unit: newUnit([]*proto.Stream{ + {Id: "stream-muted", Source: muteBothSource}, + }), + inputLevelStatus: StatusUpdate{status.Failed, Failed}, + streamStates: map[string]StatusUpdate{ + "stream-muted": {status.Running, Healthy}, + }, + expectedUnitStatus: client.UnitStateFailed, + expectedUnitMsg: Failed, + }, + { + name: "mute degraded only still reports failed", + unit: newUnit([]*proto.Stream{ + {Id: "stream-a", Source: muteDegradedSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-a": {status.Failed, Failed}, + }, + expectedUnitStatus: client.UnitStateFailed, + expectedUnitMsg: Failed, + }, + { + name: "mute degraded only suppresses degraded", + unit: newUnit([]*proto.Stream{ + {Id: "stream-a", Source: muteDegradedSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-a": {status.Degraded, Degraded}, + }, + expectedUnitStatus: client.UnitStateHealthy, + expectedUnitMsg: Healthy, + }, + { + name: "mute failed only still reports degraded", + unit: newUnit([]*proto.Stream{ + {Id: "stream-a", Source: muteFailedSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-a": {status.Degraded, Degraded}, + }, + expectedUnitStatus: client.UnitStateDegraded, + expectedUnitMsg: Degraded, + }, + { + name: "mute failed only suppresses failed", + unit: newUnit([]*proto.Stream{ + {Id: "stream-a", Source: muteFailedSource}, + }), + inputLevelStatus: StatusUpdate{status.Running, Healthy}, + streamStates: map[string]StatusUpdate{ + "stream-a": {status.Failed, Failed}, + }, + expectedUnitStatus: client.UnitStateHealthy, + expectedUnitMsg: Healthy, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + aUnit := newAgentUnit(c.unit, nil) + err := aUnit.UpdateState(c.inputLevelStatus.status, c.inputLevelStatus.msg, nil) + if err != nil { + t.Fatal(err) + } + + for id, state := range c.streamStates { + aUnit.updateStateForStream(id, state.status, state.msg) + } + + if c.unit.reportedState != c.expectedUnitStatus { + t.Errorf("expected unit status %s, got %s", c.expectedUnitStatus, c.unit.reportedState) } if c.unit.reportedMsg != c.expectedUnitMsg { - t.Errorf("expected unit msg %s, got %s", c.expectedUnitStatus, aUnit.inputLevelState.state) + t.Errorf("expected unit msg %q, got %q", c.expectedUnitMsg, c.unit.reportedMsg) } }) } } +func TestGetStreamStatesParsesStatusReporting(t *testing.T) { + muteBothSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + "report_failed": false, + }, + }) + muteDegradedSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + }, + }) + explicitTrueSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": true, + "report_failed": true, + }, + }) + + expected := client.Expected{ + Config: &proto.UnitExpectedConfig{ + Id: "input-1", + Streams: []*proto.Stream{ + {Id: "stream-plain"}, + {Id: "stream-mute-both", Source: muteBothSource}, + {Id: "stream-mute-degraded", Source: muteDegradedSource}, + {Id: "stream-explicit-true", Source: explicitTrueSource}, + }, + }, + } + + states, ids := getStreamStates(expected) + + if len(states) != 4 { + t.Fatalf("expected 4 stream states, got %d", len(states)) + } + if len(ids) != 4 { + t.Fatalf("expected 4 stream IDs, got %d", len(ids)) + } + + // No source — both default to true + if !states["stream-plain"].statusReporting.reportDegraded { + t.Error("stream without Source should default reportDegraded to true") + } + if !states["stream-plain"].statusReporting.reportFailed { + t.Error("stream without Source should default reportFailed to true") + } + + // Both muted + if states["stream-mute-both"].statusReporting.reportDegraded { + t.Error("stream with report_degraded: false should have reportDegraded false") + } + if states["stream-mute-both"].statusReporting.reportFailed { + t.Error("stream with report_failed: false should have reportFailed false") + } + + // Only degraded muted, failed defaults to true + if states["stream-mute-degraded"].statusReporting.reportDegraded { + t.Error("stream with report_degraded: false should have reportDegraded false") + } + if !states["stream-mute-degraded"].statusReporting.reportFailed { + t.Error("stream without report_failed should default reportFailed to true") + } + + // Explicit true — same as defaults + if !states["stream-explicit-true"].statusReporting.reportDegraded { + t.Error("stream with report_degraded: true should have reportDegraded true") + } + if !states["stream-explicit-true"].statusReporting.reportFailed { + t.Error("stream with report_failed: true should have reportFailed true") + } +} + +func TestGetStreamStatesInputLevelInheritance(t *testing.T) { + inputSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + "report_failed": false, + }, + }) + streamOverrideSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": true, + }, + }) + + expected := client.Expected{ + Config: &proto.UnitExpectedConfig{ + Id: "input-1", + Source: inputSource, + Streams: []*proto.Stream{ + {Id: "stream-inherit"}, + {Id: "stream-override", Source: streamOverrideSource}, + }, + }, + } + + states, _ := getStreamStates(expected) + + // stream-inherit has no Source, should inherit input-level defaults + if states["stream-inherit"].statusReporting.reportDegraded { + t.Error("stream without Source should inherit input-level reportDegraded=false") + } + if states["stream-inherit"].statusReporting.reportFailed { + t.Error("stream without Source should inherit input-level reportFailed=false") + } + + // stream-override sets report_degraded=true, report_failed falls back to input-level false + if !states["stream-override"].statusReporting.reportDegraded { + t.Error("stream with report_degraded: true should override input-level value") + } + if states["stream-override"].statusReporting.reportFailed { + t.Error("stream without report_failed should inherit input-level reportFailed=false") + } +} + +func TestStatusReportingFlipRecomputesHealth(t *testing.T) { + // Simulates what update() does when a policy change flips the + // status_reporting flags on a stream that is already Degraded. + + cu := &mockClientUnitWithPayload{ + mockClientUnit: mockClientUnit{ + expected: client.Expected{ + Config: &proto.UnitExpectedConfig{ + Id: "input-1", + Streams: []*proto.Stream{ + {Id: "stream-a"}, + }, + }, + }, + }, + } + + aUnit := newAgentUnit(cu, nil) + _ = aUnit.UpdateState(status.Running, "Healthy", nil) + + // Stream degrades — unit should be Degraded (defaults: both reported) + aUnit.updateStateForStream("stream-a", status.Degraded, "connection refused") + if cu.reportedState != client.UnitStateDegraded { + t.Fatalf("expected Degraded before flip, got %s", cu.reportedState) + } + + // Simulate a policy update that sets report_degraded: false. + aUnit.mtx.Lock() + existing := aUnit.streamStates["stream-a"] + existing.statusReporting.reportDegraded = false + aUnit.streamStates["stream-a"] = existing + + state, msg := aUnit.calcState() + streamsPayload := make(map[string]interface{}, len(aUnit.streamStates)) + for id, ss := range aUnit.streamStates { + streamsPayload[id] = map[string]interface{}{ + "status": getUnitState(ss.state).String(), + "error": ss.msg, + } + } + _ = aUnit.clientUnit.UpdateState(getUnitState(state), msg, map[string]interface{}{"streams": streamsPayload}) + aUnit.mtx.Unlock() + + // Unit should now be Healthy + if cu.reportedState != client.UnitStateHealthy { + t.Errorf("expected Healthy after muting degraded, got %s", cu.reportedState) + } + + // Per-stream payload should still show Degraded + streams, ok := cu.reportedPayload["streams"].(map[string]interface{}) + if !ok { + t.Fatal("expected streams payload") + } + streamStatus, ok := streams["stream-a"].(map[string]interface{}) + if !ok { + t.Fatal("expected stream-a payload") + } + if streamStatus["status"] != client.UnitStateDegraded.String() { + t.Errorf("expected per-stream Degraded after flip, got %q", streamStatus["status"]) + } + + // Flip report_degraded back to true — unit should go Degraded again + aUnit.mtx.Lock() + existing = aUnit.streamStates["stream-a"] + existing.statusReporting.reportDegraded = true + aUnit.streamStates["stream-a"] = existing + + state, msg = aUnit.calcState() + streamsPayload = make(map[string]interface{}, len(aUnit.streamStates)) + for id, ss := range aUnit.streamStates { + streamsPayload[id] = map[string]interface{}{ + "status": getUnitState(ss.state).String(), + "error": ss.msg, + } + } + _ = aUnit.clientUnit.UpdateState(getUnitState(state), msg, map[string]interface{}{"streams": streamsPayload}) + aUnit.mtx.Unlock() + + if cu.reportedState != client.UnitStateDegraded { + t.Errorf("expected Degraded after re-enabling reporting, got %s", cu.reportedState) + } +} + +func TestMutedStreamRecovery(t *testing.T) { + mutedSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + "report_failed": false, + }, + }) + + cu := &mockClientUnit{ + expected: client.Expected{ + Config: &proto.UnitExpectedConfig{ + Id: "input-1", + Streams: []*proto.Stream{ + {Id: "stream-muted", Source: mutedSource}, + }, + }, + }, + } + + aUnit := newAgentUnit(cu, nil) + _ = aUnit.UpdateState(status.Running, "Healthy", nil) + + // Stream goes degraded — unit should stay healthy + aUnit.updateStateForStream("stream-muted", status.Degraded, "connection refused") + if cu.reportedState != client.UnitStateHealthy { + t.Errorf("expected Healthy during degradation, got %s", cu.reportedState) + } + + // Stream recovers — unit should still be healthy + aUnit.updateStateForStream("stream-muted", status.Running, "Healthy") + if cu.reportedState != client.UnitStateHealthy { + t.Errorf("expected Healthy after recovery, got %s", cu.reportedState) + } +} + +func TestMutedStreamStillReportsPerStreamStatus(t *testing.T) { + mutedSource, _ := structpb.NewStruct(map[string]interface{}{ + "status_reporting": map[string]interface{}{ + "report_degraded": false, + "report_failed": false, + }, + }) + + cu := &mockClientUnitWithPayload{ + mockClientUnit: mockClientUnit{ + expected: client.Expected{ + Config: &proto.UnitExpectedConfig{ + Id: "input-1", + Streams: []*proto.Stream{ + {Id: "stream-muted", Source: mutedSource}, + }, + }, + }, + }, + } + + aUnit := newAgentUnit(cu, nil) + _ = aUnit.UpdateState(status.Running, "Healthy", nil) + aUnit.updateStateForStream("stream-muted", status.Degraded, "connection refused") + + // Unit should be healthy (muted) + if cu.reportedState != client.UnitStateHealthy { + t.Errorf("expected unit Healthy, got %s", cu.reportedState) + } + + // But the per-stream payload should still show Degraded + streams, ok := cu.reportedPayload["streams"].(map[string]interface{}) + if !ok { + t.Fatal("expected streams in payload") + } + streamStatus, ok := streams["stream-muted"].(map[string]interface{}) + if !ok { + t.Fatal("expected stream-muted in streams payload") + } + if streamStatus["status"] != client.UnitStateDegraded.String() { + t.Errorf("expected per-stream status %q, got %q", client.UnitStateDegraded.String(), streamStatus["status"]) + } +} + type mockClientUnit struct { expected client.Expected reportedState client.UnitState @@ -213,3 +734,16 @@ func (u *mockClientUnit) UnregisterAction(_ client.Action) { func (u *mockClientUnit) RegisterDiagnosticHook(_ string, _ string, _ string, _ string, _ client.DiagnosticHook) { } + +// mockClientUnitWithPayload extends mockClientUnit to capture the payload +type mockClientUnitWithPayload struct { + mockClientUnit + reportedPayload map[string]interface{} +} + +func (u *mockClientUnitWithPayload) UpdateState(state client.UnitState, msg string, payload map[string]interface{}) error { + u.reportedState = state + u.reportedMsg = msg + u.reportedPayload = payload + return nil +}