diff --git a/agent/agent.go b/agent/agent.go index 93fea3659..e4329cc9b 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -183,15 +183,23 @@ func (a Agent) getHeartbeat(status string) (Heartbeat, error) { return Heartbeat{}, bosherr.WrapError(err, "Getting job spec") } + var numberOfProcesses *int + processes, err := a.jobSupervisor.Processes() + if err != nil { + a.logger.Debug(agentLogTag, "Failed to get processes for heartbeat: %s", err.Error()) + } else { + n := len(processes) + numberOfProcesses = &n + } hb := Heartbeat{ - Deployment: spec.Deployment, - Job: spec.JobSpec.Name, - Index: spec.Index, - JobState: status, - Vitals: vitals, - NodeID: spec.NodeID, + Deployment: spec.Deployment, + Job: spec.JobSpec.Name, + Index: spec.Index, + JobState: status, + Vitals: vitals, + NodeID: spec.NodeID, + NumberOfProcesses: numberOfProcesses, } - return hb, nil } diff --git a/agent/agent_test.go b/agent/agent_test.go index 7983ad901..5442c92ba 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -18,6 +18,7 @@ import ( fakeas "github.com/cloudfoundry/bosh-agent/v2/agent/applier/applyspec/fakes" fakeagent "github.com/cloudfoundry/bosh-agent/v2/agent/fakes" boshhandler "github.com/cloudfoundry/bosh-agent/v2/handler" + boshjobsuper "github.com/cloudfoundry/bosh-agent/v2/jobsupervisor" fakejobsuper "github.com/cloudfoundry/bosh-agent/v2/jobsupervisor/fakes" fakembus "github.com/cloudfoundry/bosh-agent/v2/mbus/fakes" "github.com/cloudfoundry/bosh-agent/v2/platform/platformfakes" @@ -125,6 +126,11 @@ func init() { //nolint:funlen,gochecknoinits } jobSupervisor.StatusStatus = "fake-state" + jobSupervisor.ProcessesStatus = []boshjobsuper.Process{ + {Name: "process1", State: "running"}, + {Name: "process2", State: "running"}, + {Name: "process3", State: "stopped"}, + } vitalService.GetReturns(boshvitals.Vitals{ Load: []string{"a", "b", "c"}, @@ -134,13 +140,15 @@ func init() { //nolint:funlen,gochecknoinits expectedJobName := "fake-job" expectedJobIndex := 1 expectedNodeID := "node-id" + expectedNumberOfProcesses := 3 expectedHb := agent.Heartbeat{ - Deployment: "FakeDeployment", - Job: &expectedJobName, - Index: &expectedJobIndex, - JobState: "fake-state", - NodeID: expectedNodeID, - Vitals: boshvitals.Vitals{Load: []string{"a", "b", "c"}}, + Deployment: "FakeDeployment", + Job: &expectedJobName, + Index: &expectedJobIndex, + JobState: "fake-state", + NodeID: expectedNodeID, + Vitals: boshvitals.Vitals{Load: []string{"a", "b", "c"}}, + NumberOfProcesses: &expectedNumberOfProcesses, } It("sends initial heartbeat", func() { @@ -247,6 +255,55 @@ func init() { //nolint:funlen,gochecknoinits }) }) + Context("when the boshAgent fails to get processes for a heartbeat", func() { + BeforeEach(func() { + jobName := "fake-job" + nodeID := "node-id" + jobIndex := 1 + specService.Spec = boshas.V1ApplySpec{ + Deployment: "FakeDeployment", + JobSpec: boshas.JobSpec{Name: &jobName}, + Index: &jobIndex, + NodeID: nodeID, + } + + jobSupervisor.StatusStatus = "fake-state" + jobSupervisor.ProcessesError = errors.New("fake-processes-error") + + vitalService.GetReturns(boshvitals.Vitals{ + Load: []string{"a", "b", "c"}, + }, nil) + + handler.KeepOnRunning() + handler.SendErr = errors.New("stop") + }) + + It("sends heartbeat with NumberOfProcesses as nil", func() { + err := boshAgent.Run() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("stop")) + Expect(err.Error()).ToNot(ContainSubstring("fake-processes-error")) + + expectedJobName := "fake-job" + expectedJobIndex := 1 + expectedHb := agent.Heartbeat{ + Deployment: "FakeDeployment", + Job: &expectedJobName, + Index: &expectedJobIndex, + JobState: "fake-state", + NodeID: "node-id", + Vitals: boshvitals.Vitals{Load: []string{"a", "b", "c"}}, + NumberOfProcesses: nil, + } + + Expect(handler.SendInputs()).To(ContainElement(fakembus.SendInput{ + Target: boshhandler.HealthMonitor, + Topic: boshhandler.Heartbeat, + Message: expectedHb, + })) + }) + }) + It("sends job monitoring alerts to health manager", func() { handler.KeepOnRunning() diff --git a/agent/heartbeat.go b/agent/heartbeat.go index b3f8e58e1..5b6b82256 100644 --- a/agent/heartbeat.go +++ b/agent/heartbeat.go @@ -8,12 +8,13 @@ import ( // https://www.pivotaltracker.com/story/show/132265151 type Heartbeat struct { - Deployment string `json:"deployment"` - Job *string `json:"job"` - Index *int `json:"index"` - JobState string `json:"job_state"` - Vitals boshvitals.Vitals `json:"vitals"` - NodeID string `json:"node_id"` + Deployment string `json:"deployment"` + Job *string `json:"job"` + Index *int `json:"index"` + JobState string `json:"job_state"` + Vitals boshvitals.Vitals `json:"vitals"` + NodeID string `json:"node_id"` + NumberOfProcesses *int `json:"number_of_processes"` } // Heartbeat payload example: diff --git a/agent/heartbeat_test.go b/agent/heartbeat_test.go index 8a5c5d756..75cd97892 100644 --- a/agent/heartbeat_test.go +++ b/agent/heartbeat_test.go @@ -16,6 +16,7 @@ func init() { //nolint:gochecknoinits It("serializes heartbeat with all fields", func() { name := "foo" index := 0 + numberOfProcesses := 3 hb := Heartbeat{ Deployment: "FakeDeployment", @@ -29,10 +30,11 @@ func init() { //nolint:gochecknoinits "persistent": boshvitals.SpecificDiskVitals{}, }, }, - NodeID: "node-id", + NodeID: "node-id", + NumberOfProcesses: &numberOfProcesses, } - expectedJSON := `{"deployment":"FakeDeployment","job":"foo","index":0,"job_state":"running","vitals":{"cpu":{},"disk":{"ephemeral":{},"persistent":{},"system":{}},"mem":{},"swap":{},"uptime":{}},"node_id":"node-id"}` + expectedJSON := `{"deployment":"FakeDeployment","job":"foo","index":0,"job_state":"running","vitals":{"cpu":{},"disk":{"ephemeral":{},"persistent":{},"system":{}},"mem":{},"swap":{},"uptime":{}},"node_id":"node-id","number_of_processes":3}` hbBytes, err := json.Marshal(hb) Expect(err).ToNot(HaveOccurred()) @@ -42,6 +44,30 @@ func init() { //nolint:gochecknoinits Context("when job name, index are not available", func() { It("serializes job name and index as nulls to indicate that there is no job assigned to this agent", func() { + numberOfProcesses := 0 + + hb := Heartbeat{ + Deployment: "FakeDeployment", + JobState: "running", + Vitals: boshvitals.Vitals{ + Disk: boshvitals.DiskVitals{ + "system": boshvitals.SpecificDiskVitals{}, + "ephemeral": boshvitals.SpecificDiskVitals{}, + "persistent": boshvitals.SpecificDiskVitals{}, + }, + }, + NodeID: "node-id", + NumberOfProcesses: &numberOfProcesses, + } + + expectedJSON := `{"deployment":"FakeDeployment","job":null,"index":null,"job_state":"running","vitals":{"cpu":{},"disk":{"ephemeral":{},"persistent":{},"system":{}},"mem":{},"swap":{},"uptime":{}},"node_id":"node-id","number_of_processes":0}` + + hbBytes, err := json.Marshal(hb) + Expect(err).ToNot(HaveOccurred()) + Expect(string(hbBytes)).To(Equal(expectedJSON)) + }) + + It("serializes NumberOfProcesses as null when not available", func() { hb := Heartbeat{ Deployment: "FakeDeployment", JobState: "running", @@ -55,7 +81,7 @@ func init() { //nolint:gochecknoinits NodeID: "node-id", } - expectedJSON := `{"deployment":"FakeDeployment","job":null,"index":null,"job_state":"running","vitals":{"cpu":{},"disk":{"ephemeral":{},"persistent":{},"system":{}},"mem":{},"swap":{},"uptime":{}},"node_id":"node-id"}` + expectedJSON := `{"deployment":"FakeDeployment","job":null,"index":null,"job_state":"running","vitals":{"cpu":{},"disk":{"ephemeral":{},"persistent":{},"system":{}},"mem":{},"swap":{},"uptime":{}},"node_id":"node-id","number_of_processes":null}` hbBytes, err := json.Marshal(hb) Expect(err).ToNot(HaveOccurred())