Skip to content

Commit

Permalink
Add ping and tests (#16)
Browse files Browse the repository at this point in the history
* Fix build issues before pushing circle ci config

* Fix build issues before pushing circle ci config

* Configure circleci config

* Add ping command and boilerplate

* Add ping command impl and tests

* Update ping.go

* Address review

* Fix tests

Signed-off-by: QuentinBisson <[email protected]>
  • Loading branch information
QuentinBisson committed Sep 29, 2022
1 parent 1638162 commit b19f7e4
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 13 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- First release.

[Unreleased]: https://github.com/giantswarm/heartbeatctl/tree/main
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# heartbeatctl

Command line tool to interact with opsgenie heartbeats.
23 changes: 22 additions & 1 deletion pkg/ctl/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,28 @@ func (c *ctl) Ping(opts *SelectorConfig) (map[string]heartbeat.PingResult, error
return nil, ErrNoSelector
}

return make(map[string]heartbeat.PingResult), nil
heartbeats, err := c.Get(opts)
if err != nil {
return nil, err
}

var pingResults = make(map[string]heartbeat.PingResult)
var failedPings = make([]string, 0)
for _, h := range heartbeats {
result, err := c.repo.Ping(context.Background(), h.Name)
if err != nil {
failedPings = append(failedPings, h.Name)
}

if result != nil {
pingResults[h.Name] = *result
}
}

if len(failedPings) > 0 {
return pingResults, fmt.Errorf("heartbeats \"%v\" failed", failedPings)
}
return pingResults, nil
}

// enableDisableHeartbeats applies given method (can be either `repo.Enable` or
Expand Down
105 changes: 93 additions & 12 deletions pkg/ctl/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
GetMethodName = "Get"
EnableMethodName = "Enable"
DisableMethodName = "Disable"
PingMethodName = "Ping"
)

func getName(h heartbeat.Heartbeat) string {
Expand Down Expand Up @@ -68,6 +69,24 @@ var _ = Describe("Adapter", func() {
adapter = ctl.NewCtl(repo)
})

When("no heartbeats are configured", func() {
BeforeEach(func() {
configuredHeartbeats = []heartbeat.Heartbeat{}
})

Context(GetMethodName, func() {
It("returns an empty list and no error", func() {
Expect(adapter.Get(&ctl.SelectorConfig{})).To(BeEmpty())
})

It("ignores selector options", func() {
Expect(adapter.Get(&ctl.SelectorConfig{
LabelSelector: "name=foo",
})).To(BeEmpty())
})
})
})

When("heartbeats are configured", func() {
BeforeEach(func() {
configuredHeartbeats = []heartbeat.Heartbeat{
Expand Down Expand Up @@ -268,25 +287,74 @@ var _ = Describe("Adapter", func() {

AssertMethodFailsFastWhenRepoCallFails(EnableMethodName)
AssertMethodFailsFastWhenRepoCallFails(DisableMethodName)
})

When("no heartbeats are configured", func() {
BeforeEach(func() {
configuredHeartbeats = []heartbeat.Heartbeat{}
})
Context(PingMethodName, func() {
var (
expected []string
expectedInfos map[string]heartbeat.PingResult = make(map[string]heartbeat.PingResult)
)

Context(GetMethodName, func() {
It("returns an empty list and no error", func() {
Expect(adapter.Get(&ctl.SelectorConfig{})).To(BeEmpty())
JustBeforeEach(func() {
expected = []string{"foo-oof1", "bar-oof2"}
for _, hbName := range expected {
ping := &heartbeat.PingResult{
Message: "PONG - Heartbeat received",
}

repo.EXPECT().Ping(gomock.Any(), hbName).Return(ping, nil)
expectedInfos[hbName] = *ping
}
})

It("ignores selector options", func() {
Expect(adapter.Get(&ctl.SelectorConfig{
LabelSelector: "name=foo",
})).To(BeEmpty())
It("calls Ping on heartbeats selected by given options", func() {
Expect(adapter.Ping(&ctl.SelectorConfig{
NameExpressions: []string{"foo.*", ".*-oof[12]"},
LabelSelector: "enabled",
FieldSelector: "alertPriority=P3",
})).To(Equal(expectedInfos))
})
})

Context(PingMethodName, func() {
It("fails fast when first repo call on a heartbeat fails", func() {
By("making first heartbeat succeed and second fail")

fooPingResult := &heartbeat.PingResult{
Message: "PONG - Heartbeat received",
}
apiErr := errors.New("API call failed")

repo.EXPECT().Ping(gomock.Any(), "foo-rab1").Return(fooPingResult, nil)
repo.EXPECT().Ping(gomock.Any(), "foo").Return(fooPingResult, nil)
repo.EXPECT().Ping(gomock.Any(), "foo-oof1").Return(nil, apiErr)

By("calling adapter method")

pingResults, err := adapter.Ping(&ctl.SelectorConfig{
NameExpressions: []string{"foo.*"},
})

By("ensuring we get an error")

Expect(err).To(SatisfyAll(
MatchError(fmt.Errorf("heartbeats \"[foo-oof1]\" failed")),
// assert that error tells us which heartbeat caused the error
WithTransform(
func(e error) string { return e.Error() },
ContainSubstring("foo-oof1"),
),
))

By("ensuring we also get info about the heartbeat that succeeded")

Expect(pingResults).To(Equal(map[string]heartbeat.PingResult{
"foo": *fooPingResult,
"foo-rab1": *fooPingResult,
}))
})
})
})

})

Describe("failure modes", func() {
Expand Down Expand Up @@ -315,6 +383,8 @@ var _ = Describe("Adapter", func() {
_, err = adapter.Enable(opts)
case DisableMethodName:
_, err = adapter.Disable(opts)
case PingMethodName:
_, err = adapter.Ping(opts)
}

Expect(err).NotTo(Succeed())
Expand All @@ -326,6 +396,7 @@ var _ = Describe("Adapter", func() {
AssertMethodPropagatesError(GetMethodName)
AssertMethodPropagatesError(EnableMethodName)
AssertMethodPropagatesError(DisableMethodName)
AssertMethodPropagatesError(PingMethodName)
})

When("no selectors are given", func() {
Expand Down Expand Up @@ -354,6 +425,16 @@ var _ = Describe("Adapter", func() {

AssertMethodFails(EnableMethodName)
AssertMethodFails(DisableMethodName)

Context(PingMethodName, func() {
It("fails", func() {
results, err := adapter.Ping(&ctl.SelectorConfig{})
Expect(err).To(MatchError(
"No selector options given, to target all heartbeats pass '.*' name expression explicitly.",
))
Expect(results).To(BeNil())
})
})
})
})
})

0 comments on commit b19f7e4

Please sign in to comment.