Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make InstanceQueryOptions public to allow for custom predicates #77

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions net.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,26 +129,26 @@ func instanceCount(apps []*Application) int {
return count
}

type instanceQueryOptions struct {
// predicate guides filtering, indicating whether to retain an instance when it returns true or
type InstanceQueryOptions struct {
// Predicate guides filtering, indicating whether to retain an instance when it returns true or
// drop it when it returns false.
predicate func(*Instance) bool
// intn behaves like the rand.Rand.Intn function, aiding in randomizing the order of the result
Predicate func(*Instance) bool
// Intn behaves like the rand.Rand.Intn function, aiding in randomizing the order of the result
// sequence when non-nil.
intn func(int) int
Intn func(int) int
}

// InstanceQueryOption is a customization supplied to instance query functions like
// GetInstancesByVIPAddress to tailor the set of instances returned.
type InstanceQueryOption func(*instanceQueryOptions) error
type InstanceQueryOption func(*InstanceQueryOptions) error

func retainIfStatusIs(status StatusType, o *instanceQueryOptions) {
if prev := o.predicate; prev != nil {
o.predicate = func(instance *Instance) bool {
func retainIfStatusIs(status StatusType, o *InstanceQueryOptions) {
if prev := o.Predicate; prev != nil {
o.Predicate = func(instance *Instance) bool {
return prev(instance) || instance.Status == status
}
} else {
o.predicate = func(instance *Instance) bool {
o.Predicate = func(instance *Instance) bool {
return instance.Status == status
}
}
Expand All @@ -158,7 +158,7 @@ func retainIfStatusIs(status StatusType, o *instanceQueryOptions) {
//
// Supplying multiple options produced by this function applies their logical disjunction.
func WithStatus(status StatusType) InstanceQueryOption {
return func(o *instanceQueryOptions) error {
return func(o *InstanceQueryOptions) error {
if len(status) == 0 {
return errors.New("invalid instance status")
}
Expand All @@ -171,23 +171,23 @@ func WithStatus(status StatusType) InstanceQueryOption {
//
// Combining this function with the options produced by WithStatus applies their logical
// disjunction.
func ThatAreUp(o *instanceQueryOptions) error {
func ThatAreUp(o *InstanceQueryOptions) error {
retainIfStatusIs(UP, o)
return nil
}

// Shuffled requests randomizing the order of the sequence of instances returned, using the default
// shared rand.Source.
func Shuffled(o *instanceQueryOptions) error {
o.intn = rand.Intn
func Shuffled(o *InstanceQueryOptions) error {
o.Intn = rand.Intn
return nil
}

// ShuffledWith requests randomizing the order of the sequence of instances returned, using the
// supplied source of random numbers.
func ShuffledWith(r *rand.Rand) InstanceQueryOption {
return func(o *instanceQueryOptions) error {
o.intn = r.Intn
return func(o *InstanceQueryOptions) error {
o.Intn = r.Intn
return nil
}
}
Expand All @@ -208,14 +208,14 @@ func shuffleInstances(instances []*Instance, intn func(int) int) {
}

// filterInstances returns a filtered subset of the supplied sequence of instances, retaining only those
// instances for which the supplied predicate function returns true. It returns the retained instances in
// instances for which the supplied Predicate function returns true. It returns the retained instances in
// the same order the occurred in the input sequence. Note that the returned slice may share storage with
// the input sequence.
//
// The filtering algorithm is arguably baroque, in the interest of efficiency: namely, eliminating
// allocation and copying when we can avoid it. We only need to allocate and copy elements of the
// input sequence when the result sequence contains at least two nonadjacent subsequences of the
// input sequence. That is, if the predicate is, say, retaining only instances with status "UP", we
// input sequence. That is, if the Predicate is, say, retaining only instances with status "UP", we
// can avoid copying elements and instead return a subsequence of the input sequence in the
// following cases (where "U" indicates an instance with status "UP", "d" with status "DOWN"):
//
Expand Down Expand Up @@ -265,7 +265,7 @@ func shuffleInstances(instances []*Instance, intn func(int) int) {
// Continue collecting any remaining retained elements into the array.
// Return the populated subsequence of the array.
//
// The algorithm evaluates the predicate exactly once for each element of the input sequence.
// The algorithm evaluates the Predicate exactly once for each element of the input sequence.
func filterInstances(instances []*Instance, pred func(*Instance) bool) []*Instance {
for firstBegin, instance := range instances {
if !pred(instance) {
Expand Down Expand Up @@ -315,7 +315,7 @@ func filterInstancesInApps(apps []*Application, pred func(*Instance) bool) []*In
}
}

func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, opts instanceQueryOptions) ([]*Instance, error) {
func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, opts InstanceQueryOptions) ([]*Instance, error) {
var slug string
if secure {
slug = EurekaURLSlugs["InstancesBySecureVIPAddress"]
Expand Down Expand Up @@ -344,7 +344,7 @@ func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, op
return nil, err
}
var instances []*Instance
if pred := opts.predicate; pred != nil {
if pred := opts.Predicate; pred != nil {
instances = filterInstancesInApps(r.Applications, pred)
} else {
switch len(r.Applications) {
Expand All @@ -359,25 +359,25 @@ func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, op
}
}
}
if intn := opts.intn; intn != nil {
if intn := opts.Intn; intn != nil {
shuffleInstances(instances, intn)
}
return instances, nil
}

func mergeInstanceQueryOptions(defaults instanceQueryOptions, opts []InstanceQueryOption) (instanceQueryOptions, error) {
func mergeInstanceQueryOptions(defaults InstanceQueryOptions, opts []InstanceQueryOption) (InstanceQueryOptions, error) {
for _, o := range opts {
if o != nil {
if err := o(&defaults); err != nil {
return instanceQueryOptions{}, err
return InstanceQueryOptions{}, err
}
}
}
return defaults, nil
}

func collectInstanceQueryOptions(opts []InstanceQueryOption) (instanceQueryOptions, error) {
return mergeInstanceQueryOptions(instanceQueryOptions{}, opts)
func collectInstanceQueryOptions(opts []InstanceQueryOption) (InstanceQueryOptions, error) {
return mergeInstanceQueryOptions(InstanceQueryOptions{}, opts)
}

// GetInstancesByVIPAddress returns the set of instances registered with the given VIP address,
Expand Down Expand Up @@ -435,7 +435,7 @@ func scheduleInstanceUpdates(d time.Duration, produce func() ([]*Instance, error
return c
}

func (e *EurekaConnection) scheduleVIPAddressUpdates(addr string, secure bool, await bool, done <-chan struct{}, opts instanceQueryOptions) <-chan InstanceSetUpdate {
func (e *EurekaConnection) scheduleVIPAddressUpdates(addr string, secure bool, await bool, done <-chan struct{}, opts InstanceQueryOptions) <-chan InstanceSetUpdate {
produce := func() ([]*Instance, error) {
return e.getInstancesByVIPAddress(addr, secure, opts)
}
Expand Down Expand Up @@ -467,8 +467,8 @@ func (e *EurekaConnection) makeInstanceProducerForApp(name string, opts []Instan
if err != nil {
return nil, err
}
predicate := options.predicate
intn := options.intn
predicate := options.Predicate
intn := options.Intn
return func() ([]*Instance, error) {
app, err := e.GetApp(name)
if err != nil {
Expand Down Expand Up @@ -520,7 +520,7 @@ func (e *EurekaConnection) newInstanceSetSourceFor(produce func() ([]*Instance,
}
// NB: If an application contained no instances, such that it either lacked the "instance" field
// entirely or had it present but with a "null" value, or none of the present instances
// satisfied the filtering predicate, then it's possible that the slice returned by
// satisfied the filtering Predicate, then it's possible that the slice returned by
// getInstancesByVIPAddress (or similar) will be nil. Make it possible to discern when we've
// received at least one update in Latest by never storing a nil value for a successful update.
if await {
Expand Down Expand Up @@ -549,7 +549,7 @@ func (e *EurekaConnection) newInstanceSetSourceFor(produce func() ([]*Instance,
return s
}

func (e *EurekaConnection) newInstanceSetSourceForVIPAddress(addr string, secure bool, await bool, opts instanceQueryOptions) *InstanceSetSource {
func (e *EurekaConnection) newInstanceSetSourceForVIPAddress(addr string, secure bool, await bool, opts InstanceQueryOptions) *InstanceSetSource {
produce := func() ([]*Instance, error) {
return e.getInstancesByVIPAddress(addr, secure, opts)
}
Expand Down
35 changes: 18 additions & 17 deletions net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import (
)

func instancePredicateFrom(t *testing.T, opts ...InstanceQueryOption) func(*Instance) bool {
var mergedOptions instanceQueryOptions
var mergedOptions InstanceQueryOptions
for _, o := range opts {
if err := o(&mergedOptions); err != nil {
t.Fatal(err)
}
}
if pred := mergedOptions.predicate; pred != nil {
if pred := mergedOptions.Predicate; pred != nil {
return pred
}
t.Fatal("no predicate available")
t.Fatal("no Predicate available")
panic("unreachable")
}

Expand All @@ -42,22 +42,22 @@ func (s *countingSource) Reset() {
}

func TestInstanceQueryOptions(t *testing.T) {
Convey("A status predicate", t, func() {
Convey("A status Predicate", t, func() {
Convey("mandates a nonempty status", func() {
var opts instanceQueryOptions
var opts InstanceQueryOptions
err := WithStatus("")(&opts)
So(err, ShouldNotBeNil)
So(opts.predicate, ShouldBeNil)
So(opts.Predicate, ShouldBeNil)
})
matchesStatus := func(pred func(*Instance) bool, status StatusType) bool {
return pred(&Instance{Status: status})
}
Convey("matches a single status", func() {
var opts instanceQueryOptions
var opts InstanceQueryOptions
desiredStatus := UNKNOWN
err := WithStatus(desiredStatus)(&opts)
So(err, ShouldBeNil)
pred := opts.predicate
pred := opts.Predicate
So(pred, ShouldNotBeNil)
So(matchesStatus(pred, desiredStatus), ShouldBeTrue)
for _, status := range []StatusType{UP, DOWN, STARTING, OUTOFSERVICE} {
Expand All @@ -66,13 +66,13 @@ func TestInstanceQueryOptions(t *testing.T) {
}
})
Convey("matches a set of states", func() {
var opts instanceQueryOptions
var opts InstanceQueryOptions
desiredStates := []StatusType{DOWN, OUTOFSERVICE}
for _, status := range desiredStates {
err := WithStatus(status)(&opts)
So(err, ShouldBeNil)
}
pred := opts.predicate
pred := opts.Predicate
So(pred, ShouldNotBeNil)
for _, status := range desiredStates {
So(matchesStatus(pred, status), ShouldBeTrue)
Expand All @@ -85,27 +85,27 @@ func TestInstanceQueryOptions(t *testing.T) {
})
Convey("A shuffling directive", t, func() {
Convey("using the global Rand instance", func() {
var opts instanceQueryOptions
var opts InstanceQueryOptions
err := Shuffled(&opts)
So(err, ShouldBeNil)
So(opts.intn, ShouldNotBeNil)
So(opts.intn(1), ShouldEqual, 0)
So(opts.Intn, ShouldNotBeNil)
So(opts.Intn(1), ShouldEqual, 0)
})
Convey("using a specific Rand instance", func() {
source := countingSource{}
var opts instanceQueryOptions
var opts InstanceQueryOptions
err := ShuffledWith(rand.New(&source))(&opts)
So(err, ShouldBeNil)
So(opts.intn, ShouldNotBeNil)
So(opts.Intn, ShouldNotBeNil)
So(source.callCount, ShouldEqual, 0)
So(opts.intn(2), ShouldEqual, 0)
So(opts.Intn(2), ShouldEqual, 0)
So(source.callCount, ShouldEqual, 1)
})
})
}

func TestFilterInstancesInApps(t *testing.T) {
Convey("A predicate should preserve only those instances", t, func() {
Convey("A Predicate should preserve only those instances", t, func() {
Convey("with status UP", func() {
areUp := instancePredicateFrom(t, ThatAreUp)
Convey("from an empty set of applications", func() {
Expand Down Expand Up @@ -213,6 +213,7 @@ func TestFilterInstancesInApps(t *testing.T) {
})
})
}

// Preclude compiler optimization eliding the filter procedure.
var filterBenchmarkResult []*Instance

Expand Down