Skip to content

Commit

Permalink
New methods (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
ders authored Feb 14, 2024
1 parent ad94a6a commit f730838
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 26 deletions.
34 changes: 26 additions & 8 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,23 @@ import (
type Collection struct {
Monitor
sync.Mutex
services []Service
services map[string]Service
id string
updates chan Notification
}

// Verifies that a Monitor implements the Service interface.
var _ Service = &Collection{}

// Add adds a service to be monitored. Panics if the service has already been added.
func (c *Collection) Add(s Service) {
// Add adds a service to be monitored. Panics if the service has already been added. Panics if the label has been
// used already for another service.
func (c *Collection) Add(label string, s Service) {
c.Lock()
defer c.Unlock()

// Initialize the update channel if this is the first service to be added.
if c.updates == nil {
if c.services == nil {
c.services = make(map[string]Service)
c.updates = make(chan Notification)
go func() {
for range c.updates {
Expand All @@ -43,9 +45,14 @@ func (c *Collection) Add(s Service) {
// Using the same ID to subscribe to all monitored services means that Subscribe will panic below if a service
// is added twice.
c.id = "collection-" + strconv.Itoa(rand.Int())
} else {
// Otherwise check that we're not reusing a label.
if _, ok := c.services[label]; ok {
panic("add: label " + label + " already in use")
}
}

c.services = append(c.services, s)
c.services[label] = s
s.Subscribe(c.id, c.updates)

// Trigger an update to include the state of the newly added service.
Expand All @@ -61,13 +68,25 @@ func (c *Collection) StateCount(state State) int {

var n int
for _, service := range c.services {
if s, _ := service.GetState(); s == state {
if service.GetState() == state {
n++
}
}
return n
}

// Up returns a map whose keys are the labels of all the currently monitored services and whose values are true if
// the service is ready and false otherwise.
func (c *Collection) Up() map[string]bool {
up := make(map[string]bool)
c.Lock()
defer c.Unlock()
for label, service := range c.services {
up[label] = service.GetState() == Ready
}
return up
}

// Stop stops the collection and all monitored services and releases all of the resources. Neither the collection nor
// any of the services should be used after calling stop.
func (c *Collection) Stop() {
Expand Down Expand Up @@ -118,8 +137,7 @@ func (c *Collection) getOverallState() State {

we := State(Ready)
for _, service := range c.services {
state, _ := service.GetState()
switch state {
switch service.GetState() {
case Stopped:
return Stopped
case NotReady:
Expand Down
53 changes: 45 additions & 8 deletions collection_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nested

import (
"errors"
"testing"
"time"
)
Expand All @@ -10,32 +11,40 @@ func TestCollection(t *testing.T) {
co := Collection{}

// A new collection is not ready.
s, e := co.GetState()
s, e := co.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{}, co.Up())

// Add services.
s0, s1 := &Monitor{}, &Monitor{}
co.Add(s0)
co.Add(s1)
co.Add("service 0", s0)
co.Add("service 1", s1)
time.Sleep(10 * time.Millisecond)
s, e = co.GetState()
s, e = co.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{"service 0": false, "service 1": false}, co.Up())

// One service is ready.
s0.SetState(Ready, nil)
time.Sleep(10 * time.Millisecond)
s, e = co.GetState()
s, e = co.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{"service 0": true, "service 1": false}, co.Up())

// Both services are ready.
s1.SetState(Ready, nil)
time.Sleep(10 * time.Millisecond)
s, e = co.GetState()
s, e = co.GetFullState()
assertEqual(t, Ready, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{"service 0": true, "service 1": true}, co.Up())

assertEqual(t, 2, co.StateCount(Ready))
assertEqual(t, 0, co.StateCount(NotReady))
Expand All @@ -44,9 +53,11 @@ func TestCollection(t *testing.T) {
// One service is stopped.
s0.Stop()
time.Sleep(10 * time.Millisecond)
s, e = co.GetState()
s, e = co.GetFullState()
assertEqual(t, Stopped, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{"service 0": false, "service 1": true}, co.Up())

assertEqual(t, 1, co.StateCount(Ready))
assertEqual(t, 0, co.StateCount(NotReady))
Expand All @@ -55,15 +66,41 @@ func TestCollection(t *testing.T) {
// One service is stopped, and the other is not ready.
s1.SetState(NotReady, nil)
time.Sleep(10 * time.Millisecond)
s, e = co.GetState()
s, e = co.GetFullState()
assertEqual(t, Stopped, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{"service 0": false, "service 1": false}, co.Up())

// Stop all services.
co.Stop()
assertEqual(t, 0, co.StateCount(Ready))
assertEqual(t, 0, co.StateCount(NotReady))
assertEqual(t, map[string]bool{}, co.Up())

// We also have no stopped services because the service list has been emptied.
assertEqual(t, 0, co.StateCount(Stopped))
}

func TestCollection2(t *testing.T) {

co := Collection{}

// A new collection is not ready.
s, e := co.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, nil, e)

// Add two services; one is ready, one isn't.
s0, s1 := &Monitor{}, &Monitor{}
s0.SetState(Ready, nil)
co.Add("service 0", s0)
s1.SetState(NotReady, errors.New("oh, no!"))
co.Add("service 1", s1)
time.Sleep(10 * time.Millisecond)
s, e = co.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, nil, e)
assertEqual(t, s, co.GetState())
assertEqual(t, map[string]bool{"service 0": true, "service 1": false}, co.Up())
}
9 changes: 8 additions & 1 deletion monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ type Monitor struct {
var _ Service = &Monitor{}

// GetState returns the current state of the service.
func (m *Monitor) GetState() (State, error) {
func (m *Monitor) GetState() State {
m.Lock()
defer m.Unlock()
return m.state
}

// GetFullState returns the current state and error state of the service.
func (m *Monitor) GetFullState() (State, error) {
m.Lock()
defer m.Unlock()
return m.state, m.err
Expand Down
14 changes: 7 additions & 7 deletions monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ func TestMonitor(t *testing.T) {

// A new Monitor is not ready.
mon := Monitor{}
s, e := mon.GetState()
s, e := mon.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, nil, e)

// Set to ready.
mon.SetState(Ready, nil)
s, e = mon.GetState()
s, e = mon.GetFullState()
assertEqual(t, Ready, s)
assertEqual(t, nil, e)

// Set to not ready with a reason.
reason := errors.New("some reason")
mon.SetState(NotReady, reason)
s, e = mon.GetState()
s, e = mon.GetFullState()
assertEqual(t, NotReady, s)
assertEqual(t, reason, e)

Expand All @@ -59,13 +59,13 @@ func TestMonitor(t *testing.T) {

// Set ready again.
mon.SetState(Ready, nil)
s, e = mon.GetState()
s, e = mon.GetFullState()
assertEqual(t, Ready, s)
assertEqual(t, nil, e)

// Stop.
mon.Stop()
s, e = mon.GetState()
s, e = mon.GetFullState()
assertEqual(t, Stopped, s)
assertEqual(t, nil, e)

Expand All @@ -80,13 +80,13 @@ func TestMonitor2(t *testing.T) {
// Failure on initialization.
failure := errors.New("some failure")
mon.SetState(Stopped, failure)
s, e := mon.GetState()
s, e := mon.GetFullState()
assertEqual(t, Stopped, s)
assertEqual(t, failure, e)

// Now Stop() should be a no-op
mon.Stop()
s, e = mon.GetState()
s, e = mon.GetFullState()
assertEqual(t, Stopped, s)
assertEqual(t, failure, e) // note that the error condition is still there
}
Expand Down
6 changes: 4 additions & 2 deletions nested.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ func (s State) String() string {
}

type Service interface {
// GetState returns the current state and error state of the service.
GetState() (State, error)
// GetState returns the current state of the service.
GetState() State
// GetFullState returns the current state and error state of the service.
GetFullState() (State, error)
// Stop stops the service, and releases all resources. After sending the final update to the stopped state,
// all subscriptions are unsubscribed. Future calls to GetState() will always return Stopped.
Stop()
Expand Down

0 comments on commit f730838

Please sign in to comment.