<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// TODO(wallyworld) bug http://pad.lv/1408459
// Re-enable tests for i386 when these tests are fixed to work on that architecture.

// +build !386

package apiserver_test

import (
	"time"

	"github.com/juju/loggo"
	gitjujutesting "github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/api"
	"github.com/juju/juju/apiserver"
	"github.com/juju/juju/juju/testing"
	"github.com/juju/juju/rpc"
	coretesting "github.com/juju/juju/testing"
)

type pingerSuite struct {
	testing.JujuConnSuite
}

var _ = gc.Suite(&pingerSuite{})

var testPingPeriod = 100 * time.Millisecond

func (s *pingerSuite) TestConnectionBrokenDetection(c *gc.C) {
	s.PatchValue(&api.PingPeriod, testPingPeriod)

	st, _ := s.OpenAPIAsNewMachine(c)

	// Connection still alive
	select {
	case <-time.After(testPingPeriod):
	case <-st.Broken():
		c.Fatalf("connection should be alive still")
	}

	// Close the connection and see if we detect this
	go st.Close()

	// Check it's detected
	select {
	case <-time.After(testPingPeriod + time.Second):
		c.Fatalf("connection not closed as expected")
	case <-st.Broken():
		return
	}
}

func (s *pingerSuite) TestPing(c *gc.C) {
	tw := &loggo.TestWriter{}
	c.Assert(loggo.RegisterWriter("ping-tester", tw, loggo.DEBUG), gc.IsNil)
	defer loggo.RemoveWriter("ping-tester")

	st, _ := s.OpenAPIAsNewMachine(c)
	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)
	err = st.Close()
	c.Assert(err, jc.ErrorIsNil)
	err = st.Ping()
	c.Assert(err, gc.Equals, rpc.ErrShutdown)

	// Make sure that ping messages have not been logged.
	for _, m := range tw.Log() {
		c.Logf("checking %q", m.Message)
		c.Check(m.Message, gc.Not(gc.Matches), `.*"Request":"Ping".*`)
	}
}

func (s *pingerSuite) TestClientNoNeedToPing(c *gc.C) {
	s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(0))
	st, err := api.Open(s.APIInfo(c), api.DefaultDialOpts())
	c.Assert(err, jc.ErrorIsNil)
	defer st.Close()
	time.Sleep(coretesting.ShortWait)
	err = st.Ping()
	c.Assert(err, jc.ErrorIsNil)
}

func (s *pingerSuite) TestAgentConnectionShutsDownWithNoPing(c *gc.C) {
	s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(0))
	st, _ := s.OpenAPIAsNewMachine(c)
	time.Sleep(coretesting.ShortWait)
	err := st.Ping()
	c.Assert(err, gc.ErrorMatches, "connection is shut down")
}

func (s *pingerSuite) calculatePingTimeout(c *gc.C) time.Duration {
	openStart := time.Now()
	st, _ := s.OpenAPIAsNewMachine(c)
	defer st.Close()

	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)
	openDelay := time.Since(openStart)
	pingTimeout := openDelay + openDelay/10
	c.Logf("API open and initial ping took %v; using ping timeout %v", openDelay, pingTimeout)
	return pingTimeout
}

func (s *pingerSuite) TestAgentConnectionDelaysShutdownWithPing(c *gc.C) {
	// To negate the effects of an underpowered or heavily loaded
	// machine running this test, tune the shortTimeout based on the
	// time it takes to open an API connection +10%.
	shortTimeout := s.calculatePingTimeout(c)
	attemptDelay := shortTimeout / 4

	s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(shortTimeout))

	st, _ := s.OpenAPIAsNewMachine(c)
	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)

	// As long as we don't wait too long, the connection stays open
	attempt := utils.AttemptStrategy{
		Min:   10,
		Delay: attemptDelay,
	}
	testStart := time.Now()
	c.Logf(
		"pinging %d times with %v delay, starting at %v",
		attempt.Min, attempt.Delay, testStart,
	)
	var lastLoop time.Time
	for a := attempt.Start(); a.Next(); {
		testNow := time.Now()
		loopDelta := testNow.Sub(lastLoop)
		if lastLoop.IsZero() {
			loopDelta = 0
		}
		c.Logf("duration since last ping: %v", loopDelta)
		err = st.Ping()
		if !c.Check(
			err, jc.ErrorIsNil,
			gc.Commentf(
				"ping timeout exceeded at %v (%v since the test start)",
				testNow, testNow.Sub(testStart),
			),
		) {
			c.Check(err, gc.ErrorMatches, "connection is shut down")
			return
		}
		lastLoop = time.Now()
	}

	// However, once we stop pinging for too long, the connection dies
	time.Sleep(2 * shortTimeout) // Exceed the timeout.
	err = st.Ping()
	c.Assert(err, gc.ErrorMatches, "connection is shut down")
}

type mongoPingerSuite struct {
	testing.JujuConnSuite
}

var _ = gc.Suite(&mongoPingerSuite{})

func (s *mongoPingerSuite) SetUpSuite(c *gc.C) {
	s.JujuConnSuite.SetUpSuite(c)
	// We need to set the ping interval before the server is started in test setup.
	restore := gitjujutesting.PatchValue(apiserver.MongoPingInterval, coretesting.ShortWait)
	s.AddSuiteCleanup(func(*gc.C) { restore() })
}

func (s *mongoPingerSuite) TestAgentConnectionsShutDownWhenStateDies(c *gc.C) {
	st, _ := s.OpenAPIAsNewMachine(c)
	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)
	gitjujutesting.MgoServer.Destroy()

	attempt := utils.AttemptStrategy{
		Total: coretesting.LongWait,
		Delay: coretesting.ShortWait,
	}
	for a := attempt.Start(); a.Next(); {
		if err := st.Ping(); err != nil {
			c.Assert(err, gc.ErrorMatches, "connection is shut down")
			return
		}
	}
	c.Fatalf("timed out waiting for API server to die")
}
<commit_msg>Changes after reviews<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// TODO(wallyworld) bug http://pad.lv/1408459
// Re-enable tests for i386 when these tests are fixed to work on that architecture.

// +build !386

package apiserver_test

import (
	"time"

	"github.com/juju/loggo"
	gitjujutesting "github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/api"
	"github.com/juju/juju/apiserver"
	"github.com/juju/juju/juju/testing"
	"github.com/juju/juju/rpc"
	coretesting "github.com/juju/juju/testing"
)

type pingerSuite struct {
	testing.JujuConnSuite
}

var _ = gc.Suite(&pingerSuite{})

var testPingPeriod = 100 * time.Millisecond

func (s *pingerSuite) TestConnectionBrokenDetection(c *gc.C) {
	s.PatchValue(&api.PingPeriod, testPingPeriod)

	st, _ := s.OpenAPIAsNewMachine(c)

	// Connection still alive
	select {
	case <-time.After(testPingPeriod):
	case <-st.Broken():
		c.Fatalf("connection should be alive still")
	}

	// Close the connection and see if we detect this
	go st.Close()

	// Check it's detected
	select {
	case <-time.After(testPingPeriod + time.Second):
		c.Fatalf("connection not closed as expected")
	case <-st.Broken():
		return
	}
}

func (s *pingerSuite) TestPing(c *gc.C) {
	tw := &loggo.TestWriter{}
	c.Assert(loggo.RegisterWriter("ping-tester", tw, loggo.DEBUG), gc.IsNil)
	defer loggo.RemoveWriter("ping-tester")

	st, _ := s.OpenAPIAsNewMachine(c)
	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)
	err = st.Close()
	c.Assert(err, jc.ErrorIsNil)
	err = st.Ping()
	c.Assert(err, gc.Equals, rpc.ErrShutdown)

	// Make sure that ping messages have not been logged.
	for _, m := range tw.Log() {
		c.Logf("checking %q", m.Message)
		c.Check(m.Message, gc.Not(gc.Matches), `.*"Request":"Ping".*`)
	}
}

func (s *pingerSuite) TestClientNoNeedToPing(c *gc.C) {
	s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(0))
	st, err := api.Open(s.APIInfo(c), api.DefaultDialOpts())
	c.Assert(err, jc.ErrorIsNil)
	defer st.Close()
	time.Sleep(coretesting.ShortWait)
	err = st.Ping()
	c.Assert(err, jc.ErrorIsNil)
}

func (s *pingerSuite) TestAgentConnectionShutsDownWithNoPing(c *gc.C) {
	s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(0))
	st, _ := s.OpenAPIAsNewMachine(c)
	time.Sleep(coretesting.ShortWait)
	err := st.Ping()
	c.Assert(err, gc.ErrorMatches, "connection is shut down")
}

func (s *pingerSuite) calculatePingTimeout(c *gc.C) time.Duration {
	// Try opening an API connection a few times and take the max
	// delay among the attempts.
	attempt := utils.AttemptStrategy{
		Delay: coretesting.ShortWait,
		Min:   3,
	}
	var maxTimeout time.Duration
	for a := attempt.Start(); a.Next(); {
		openStart := time.Now()
		st, _ := s.OpenAPIAsNewMachine(c)
		err := st.Ping()
		if c.Check(err, jc.ErrorIsNil) {
			openDelay := time.Since(openStart)
			c.Logf("API open and initial ping took %v", openDelay)
			if maxTimeout < openDelay {
				maxTimeout = openDelay
			}
		}
		if st != nil {
			c.Check(st.Close(), jc.ErrorIsNil)
		}
	}
	if !c.Failed() && maxTimeout > 0 {
		return maxTimeout
	}
	c.Fatalf("cannot calculate ping timeout")
	return 0
}

func (s *pingerSuite) TestAgentConnectionDelaysShutdownWithPing(c *gc.C) {
	// To negate the effects of an underpowered or heavily loaded
	// machine running this test, tune the shortTimeout based on the
	// maximum duration it takes to open an API connection.
	shortTimeout := s.calculatePingTimeout(c)
	attemptDelay := shortTimeout / 4

	s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(shortTimeout))

	st, _ := s.OpenAPIAsNewMachine(c)
	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)
	defer st.Close()

	// As long as we don't wait too long, the connection stays open
	attempt := utils.AttemptStrategy{
		Min:   10,
		Delay: attemptDelay,
	}
	testStart := time.Now()
	c.Logf(
		"pinging %d times with %v delay, ping timeout %v, starting at %v",
		attempt.Min, attempt.Delay, shortTimeout, testStart,
	)
	var lastLoop time.Time
	for a := attempt.Start(); a.Next(); {
		testNow := time.Now()
		loopDelta := testNow.Sub(lastLoop)
		if lastLoop.IsZero() {
			loopDelta = 0
		}
		c.Logf("duration since last ping: %v", loopDelta)
		err = st.Ping()
		if !c.Check(
			err, jc.ErrorIsNil,
			gc.Commentf(
				"ping timeout exceeded at %v (%v since the test start)",
				testNow, testNow.Sub(testStart),
			),
		) {
			c.Check(err, gc.ErrorMatches, "connection is shut down")
			return
		}
		lastLoop = time.Now()
	}

	// However, once we stop pinging for too long, the connection dies
	time.Sleep(2 * shortTimeout) // Exceed the timeout.
	err = st.Ping()
	c.Assert(err, gc.ErrorMatches, "connection is shut down")
}

type mongoPingerSuite struct {
	testing.JujuConnSuite
}

var _ = gc.Suite(&mongoPingerSuite{})

func (s *mongoPingerSuite) SetUpSuite(c *gc.C) {
	s.JujuConnSuite.SetUpSuite(c)
	// We need to set the ping interval before the server is started in test setup.
	restore := gitjujutesting.PatchValue(apiserver.MongoPingInterval, coretesting.ShortWait)
	s.AddSuiteCleanup(func(*gc.C) { restore() })
}

func (s *mongoPingerSuite) TestAgentConnectionsShutDownWhenStateDies(c *gc.C) {
	st, _ := s.OpenAPIAsNewMachine(c)
	err := st.Ping()
	c.Assert(err, jc.ErrorIsNil)
	gitjujutesting.MgoServer.Destroy()

	attempt := utils.AttemptStrategy{
		Total: coretesting.LongWait,
		Delay: coretesting.ShortWait,
	}
	for a := attempt.Start(); a.Next(); {
		if err := st.Ping(); err != nil {
			c.Assert(err, gc.ErrorMatches, "connection is shut down")
			return
		}
	}
	c.Fatalf("timed out waiting for API server to die")
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Provides the low-level mechanisms to manipulate mixer attributes.
//
// Attributes are name/value pairs providing metadata about an operation.
// In the Istio system, the proxy supplies a potentially large number of
// attributes to the mixer for every request.
//
// There is an 'attribute protocol' between the proxy and the mixer which
// this package implements.
//
// The general use of this package is that there is a single Manager instance
// created for the mixer as a whole. Each incoming gRPC stream uses the manager
// to create a Tracker which is responsible for implementing the aforementioned
// attribute protocol.
//
// The caller invokes the tracker for every incoming request and pushes into it
// an Attributes proto. Doing so produces an attribute Bag which holds the current
// value of all known attributes.
package attribute

// Manager implements the attribute protocol for the mixer's API
//
// There is typically:
//
// * One Manager per mixer instance
// * One Tracker per incoming gRPC streams to the mixer
// * One Bag per attribute context created on individual gRPC streams
type Manager interface {
	// NewTracker returns a Tracker.
	//
	// This method is thread-safe
	NewTracker() Tracker
}

type manager struct {
	dictionaries dictionaries
}

// NewManager allocates a fresh Manager.
//
// There is typically a single Manager allocated per mixer.
func NewManager() Manager {
	return &manager{}
}

func (am *manager) NewTracker() Tracker {
	return getTracker(&am.dictionaries)
}
<commit_msg>Enable more linters.<commit_after>// Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package attribute provides the low-level mechanisms to manipulate mixer attributes.
//
// Attributes are name/value pairs providing metadata about an operation.
// In the Istio system, the proxy supplies a potentially large number of
// attributes to the mixer for every request.
//
// There is an 'attribute protocol' between the proxy and the mixer which
// this package implements.
//
// The general use of this package is that there is a single Manager instance
// created for the mixer as a whole. Each incoming gRPC stream uses the manager
// to create a Tracker which is responsible for implementing the aforementioned
// attribute protocol.
//
// The caller invokes the tracker for every incoming request and pushes into it
// an Attributes proto. Doing so produces an attribute Bag which holds the current
// value of all known attributes.
package attribute

// Manager implements the attribute protocol for the mixer's API
//
// There is typically:
//
// * One Manager per mixer instance
// * One Tracker per incoming gRPC streams to the mixer
// * One Bag per attribute context created on individual gRPC streams
type Manager interface {
	// NewTracker returns a Tracker.
	//
	// This method is thread-safe
	NewTracker() Tracker
}

type manager struct {
	dictionaries dictionaries
}

// NewManager allocates a fresh Manager.
//
// There is typically a single Manager allocated per mixer.
func NewManager() Manager {
	return &manager{}
}

func (am *manager) NewTracker() Tracker {
	return getTracker(&am.dictionaries)
}
<|endoftext|>


<commit_before>package empire

import (
	"fmt"
	"time"

	"github.com/coreos/fleet/log"
	"github.com/remind101/empire/scheduler"
)

// Manager is responsible for talking to the scheduler to schedule jobs onto the
// cluster.
type Manager interface {
	// ScheduleRelease schedules a release onto the cluster.
	ScheduleRelease(*Release, *Config, *Slug, Formation) error

	// ScaleRelease scales a release based on a process quantity map.
	ScaleRelease(*Release, *Config, *Slug, Formation, ProcessQuantityMap) error

	// FindJobsByApp returns JobStates for an app.
	JobStatesByApp(*App) ([]*JobState, error)
}

// manager is a base implementation of the Manager interface.
type manager struct {
	scheduler.Scheduler
	JobsRepository
}

// ScheduleRelease creates jobs for every process and instance count and
// schedules them onto the cluster.
func (m *manager) ScheduleRelease(release *Release, config *Config, slug *Slug, formation Formation) error {
	// Find any existing jobs that have been scheduled for this app.
	existing, err := m.existingJobs(release)
	if err != nil {
		return err
	}

	go func() {
		time.Sleep(time.Second * 60)
		if err := m.unscheduleMulti(existing); err != nil {
			// TODO What to do here?
			log.Errorf("Error unscheduling stale jobs: %s", err)
		}
	}()

	jobs := buildJobs(
		release.AppName,
		release.Ver,
		slug.Image,
		config.Vars,
		formation,
	)

	return m.scheduleMulti(jobs)
}

func (m *manager) existingJobs(release *Release) ([]*Job, error) {
	return m.JobsRepository.List(JobQuery{
		App:     release.AppName,
		Release: release.Ver,
	})
}

func (m *manager) scheduleMulti(jobs []*Job) error {
	for _, j := range jobs {
		if err := m.schedule(j); err != nil {
			return err
		}
	}

	return nil
}

// schedule schedules a Job and adds it to the list of scheduled jobs.
func (m *manager) schedule(j *Job) error {
	name := j.JobName()
	env := environment(j.Environment)
	exec := scheduler.Execute{
		Command: string(j.Command),
		Image: scheduler.Image{
			Repo: string(j.Image.Repo),
			ID:   j.Image.ID,
		},
	}

	// Schedule the job onto the cluster.
	if err := m.Scheduler.Schedule(&scheduler.Job{
		Name:        name,
		Environment: env,
		Execute:     exec,
	}); err != nil {
		return err
	}

	// Add it to the list of scheduled jobs.
	if err := m.JobsRepository.Add(j); err != nil {
		return err
	}

	return nil
}

func (m *manager) unscheduleMulti(jobs []*Job) error {
	for _, j := range jobs {
		if err := m.unschedule(j); err != nil {
			return err
		}
	}

	return nil
}

func (m *manager) unschedule(j *Job) error {
	return m.Scheduler.Unschedule(j.JobName())
}

// ScaleRelease takes a release and process quantity map, and
// schedules/unschedules jobs to make the formation match the quantity map
func (m *manager) ScaleRelease(release *Release, config *Config, slug *Slug, formation Formation, qm ProcessQuantityMap) error {
	for t, q := range qm {
		if p, ok := formation[t]; ok {
			if err := m.scaleProcess(release, config, slug, t, p, q); err != nil {
				return err
			}
		}
	}

	return nil
}

func (m *manager) scaleProcess(release *Release, config *Config, slug *Slug, t ProcessType, p *Process, q int) error {
	// Scale up
	if p.Quantity < q {
		for i := p.Quantity + 1; i <= q; i++ {
			err := m.schedule(
				&Job{
					AppName:        release.AppName,
					ReleaseVersion: release.Ver,
					ProcessType:    t,
					Instance:       i,
					Environment:    config.Vars,
					Image:          slug.Image,
					Command:        p.Command,
				},
			)
			if err != nil {
				return err
			}
		}
	}

	// Scale down
	if p.Quantity > q {
		for i := p.Quantity; i > q; i-- {
			err := m.Scheduler.Unschedule(newJobName(release.AppName, release.Ver, t, i))
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (m *manager) JobStatesByApp(app *App) ([]*JobState, error) {
	// Jobs expected to be running
	jobs, err := m.JobsRepository.List(JobQuery{App: app.Name})
	if err != nil {
		return nil, err
	}

	// Job states for all existing jobs
	sjs, err := m.Scheduler.JobStates()
	if err != nil {
		return nil, err
	}

	// Create a map for easy lookups
	jsm := make(map[scheduler.JobName]*scheduler.JobState, len(sjs))
	for _, js := range sjs {
		jsm[js.Name] = js
	}

	// Create JobState based on Jobs and scheduler.JobStates
	js := make([]*JobState, len(jobs))
	for i, j := range jobs {
		s, ok := jsm[j.JobName()]

		machineID := "unknown"
		state := "unknown"
		if ok {
			machineID = s.MachineID
			state = s.State
		}

		js[i] = &JobState{
			Job:       j,
			Name:      j.JobName(),
			MachineID: machineID,
			State:     state,
		}
	}

	return js, nil
}

// newJobName returns a new Name with the proper format.
func newJobName(name AppName, v ReleaseVersion, t ProcessType, i int) scheduler.JobName {
	return scheduler.JobName(fmt.Sprintf("%s.%d.%s.%d", name, v, t, i))
}

func buildJobs(name AppName, version ReleaseVersion, image Image, vars Vars, f Formation) []*Job {
	var jobs []*Job

	// Build jobs for each process type
	for t, p := range f {
		// Build a Job for each instance of the process.
		for i := 1; i <= p.Quantity; i++ {
			j := &Job{
				AppName:        name,
				ReleaseVersion: version,
				ProcessType:    t,
				Instance:       i,
				Environment:    vars,
				Image:          image,
				Command:        p.Command,
			}

			jobs = append(jobs, j)
		}
	}

	return jobs
}

// environment coerces a Vars into a map[string]string.
func environment(vars Vars) map[string]string {
	env := make(map[string]string)

	for k, v := range vars {
		env[string(k)] = string(v)
	}

	return env
}
<commit_msg>Unschedule all existing jobs for an app<commit_after>package empire

import (
	"fmt"
	"time"

	"github.com/coreos/fleet/log"
	"github.com/remind101/empire/scheduler"
)

// Manager is responsible for talking to the scheduler to schedule jobs onto the
// cluster.
type Manager interface {
	// ScheduleRelease schedules a release onto the cluster.
	ScheduleRelease(*Release, *Config, *Slug, Formation) error

	// ScaleRelease scales a release based on a process quantity map.
	ScaleRelease(*Release, *Config, *Slug, Formation, ProcessQuantityMap) error

	// FindJobsByApp returns JobStates for an app.
	JobStatesByApp(*App) ([]*JobState, error)
}

// manager is a base implementation of the Manager interface.
type manager struct {
	scheduler.Scheduler
	JobsRepository
}

// ScheduleRelease creates jobs for every process and instance count and
// schedules them onto the cluster.
func (m *manager) ScheduleRelease(release *Release, config *Config, slug *Slug, formation Formation) error {
	// Find any existing jobs that have been scheduled for this app.
	existing, err := m.existingJobs(release)
	if err != nil {
		return err
	}

	go func() {
		time.Sleep(time.Second * 60)
		if err := m.unscheduleMulti(existing); err != nil {
			// TODO What to do here?
			log.Errorf("Error unscheduling stale jobs: %s", err)
		}
	}()

	jobs := buildJobs(
		release.AppName,
		release.Ver,
		slug.Image,
		config.Vars,
		formation,
	)

	return m.scheduleMulti(jobs)
}

func (m *manager) existingJobs(release *Release) ([]*Job, error) {
	return m.JobsRepository.List(JobQuery{
		App: release.AppName,
	})
}

func (m *manager) scheduleMulti(jobs []*Job) error {
	for _, j := range jobs {
		if err := m.schedule(j); err != nil {
			return err
		}
	}

	return nil
}

// schedule schedules a Job and adds it to the list of scheduled jobs.
func (m *manager) schedule(j *Job) error {
	name := j.JobName()
	env := environment(j.Environment)
	exec := scheduler.Execute{
		Command: string(j.Command),
		Image: scheduler.Image{
			Repo: string(j.Image.Repo),
			ID:   j.Image.ID,
		},
	}

	// Schedule the job onto the cluster.
	if err := m.Scheduler.Schedule(&scheduler.Job{
		Name:        name,
		Environment: env,
		Execute:     exec,
	}); err != nil {
		return err
	}

	// Add it to the list of scheduled jobs.
	if err := m.JobsRepository.Add(j); err != nil {
		return err
	}

	return nil
}

func (m *manager) unscheduleMulti(jobs []*Job) error {
	for _, j := range jobs {
		if err := m.unschedule(j); err != nil {
			return err
		}
	}

	return nil
}

func (m *manager) unschedule(j *Job) error {
	return m.Scheduler.Unschedule(j.JobName())
}

// ScaleRelease takes a release and process quantity map, and
// schedules/unschedules jobs to make the formation match the quantity map
func (m *manager) ScaleRelease(release *Release, config *Config, slug *Slug, formation Formation, qm ProcessQuantityMap) error {
	for t, q := range qm {
		if p, ok := formation[t]; ok {
			if err := m.scaleProcess(release, config, slug, t, p, q); err != nil {
				return err
			}
		}
	}

	return nil
}

func (m *manager) scaleProcess(release *Release, config *Config, slug *Slug, t ProcessType, p *Process, q int) error {
	// Scale up
	if p.Quantity < q {
		for i := p.Quantity + 1; i <= q; i++ {
			err := m.schedule(
				&Job{
					AppName:        release.AppName,
					ReleaseVersion: release.Ver,
					ProcessType:    t,
					Instance:       i,
					Environment:    config.Vars,
					Image:          slug.Image,
					Command:        p.Command,
				},
			)
			if err != nil {
				return err
			}
		}
	}

	// Scale down
	if p.Quantity > q {
		for i := p.Quantity; i > q; i-- {
			err := m.Scheduler.Unschedule(newJobName(release.AppName, release.Ver, t, i))
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (m *manager) JobStatesByApp(app *App) ([]*JobState, error) {
	// Jobs expected to be running
	jobs, err := m.JobsRepository.List(JobQuery{App: app.Name})
	if err != nil {
		return nil, err
	}

	// Job states for all existing jobs
	sjs, err := m.Scheduler.JobStates()
	if err != nil {
		return nil, err
	}

	// Create a map for easy lookups
	jsm := make(map[scheduler.JobName]*scheduler.JobState, len(sjs))
	for _, js := range sjs {
		jsm[js.Name] = js
	}

	// Create JobState based on Jobs and scheduler.JobStates
	js := make([]*JobState, len(jobs))
	for i, j := range jobs {
		s, ok := jsm[j.JobName()]

		machineID := "unknown"
		state := "unknown"
		if ok {
			machineID = s.MachineID
			state = s.State
		}

		js[i] = &JobState{
			Job:       j,
			Name:      j.JobName(),
			MachineID: machineID,
			State:     state,
		}
	}

	return js, nil
}

// newJobName returns a new Name with the proper format.
func newJobName(name AppName, v ReleaseVersion, t ProcessType, i int) scheduler.JobName {
	return scheduler.JobName(fmt.Sprintf("%s.%d.%s.%d", name, v, t, i))
}

func buildJobs(name AppName, version ReleaseVersion, image Image, vars Vars, f Formation) []*Job {
	var jobs []*Job

	// Build jobs for each process type
	for t, p := range f {
		// Build a Job for each instance of the process.
		for i := 1; i <= p.Quantity; i++ {
			j := &Job{
				AppName:        name,
				ReleaseVersion: version,
				ProcessType:    t,
				Instance:       i,
				Environment:    vars,
				Image:          image,
				Command:        p.Command,
			}

			jobs = append(jobs, j)
		}
	}

	return jobs
}

// environment coerces a Vars into a map[string]string.
func environment(vars Vars) map[string]string {
	env := make(map[string]string)

	for k, v := range vars {
		env[string(k)] = string(v)
	}

	return env
}
<|endoftext|>


<commit_before>package fh

import (
	"context"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/firehose"
	"github.com/gallir/bytebufferpool"
	"github.com/gallir/smart-relayer/lib"
)

const (
	recordsTimeout = 15 * time.Second
	maxRecordSize  = 1000000 // The maximum size of a record sent to Kinesis Firehose, before base64-encoding, is 1000 KB
)

var (
	clientCount int64 = 0
	buffPool          = &bytebufferpool.Pool{}
)

// Client is the thread that connect to the remote redis server
type Client struct {
	sync.Mutex
	srv         *Server
	mode        int
	records     []*record
	status      int
	finish      chan bool
	done        chan bool
	ID          int
	tick        *time.Ticker
	lastFlushed time.Time
}

// NewClient creates a new client that connect to a Redis server
func NewClient(srv *Server) *Client {
	n := atomic.AddInt64(&clientCount, 1)

	clt := &Client{
		done:   make(chan bool),
		finish: make(chan bool),
		status: 0,
		srv:    srv,
		ID:     int(n),
		tick:   time.NewTicker(recordsTimeout),
	}

	if err := clt.Reload(); err != nil {
		lib.Debugf("Firehose client %d ERROR: reload %s", clt.ID, err)
		return nil
	}

	lib.Debugf("Firehose client %d ready", clt.ID)

	return clt
}

// Reload finish the listener and run it again
func (clt *Client) Reload() error {
	clt.Lock()
	defer clt.Unlock()

	if clt.status > 0 {
		clt.Exit()
	}

	go clt.listen()

	return nil
}

func (clt *Client) listen() {
	defer lib.Debugf("Firehose client %d: Closed listener", clt.ID)
	clt.status = 1

	for {
		select {
		case r := <-clt.srv.recordsCh:
			// ignore empty messages
			if r.len() <= 0 {
				continue
			}

			clt.records = append(clt.records, r)
			if len(clt.records) >= clt.srv.config.MaxRecords {
				clt.flush()
			}
		case <-clt.tick.C:
			if time.Since(clt.lastFlushed) >= recordsTimeout {
				clt.flush()
			}
		case <-clt.done:
			lib.Debugf("Firehose client %d: closing..", clt.ID)
			clt.finish <- true
			return
		}
	}

}

func (clt *Client) flush() {
	clt.lastFlushed = time.Now()

	if len(clt.records) <= 0 {
		return
	}

	records := make([]*firehose.Record, 0)

	b := buffPool.Get()

	for _, r := range clt.records {
		if r.len()+b.Len()+1 >= maxRecordSize {
			bs := append([]byte(nil), b.Bytes()...)
			b.Reset()
			records = append(records, &firehose.Record{Data: bs})
		}

		b.Write(r.bytes())
		b.Write([]byte("\n"))
	}

	if b.Len() > 0 {
		bs := append([]byte(nil), b.Bytes()...)
		buffPool.Put(b)
		records = append(records, &firehose.Record{Data: bs})
	}

	req, _ := clt.srv.awsSvc.PutRecordBatchRequest(&firehose.PutRecordBatchInput{
		DeliveryStreamName: aws.String(clt.srv.config.StreamName),
		Records:            records,
	})

	ctx, cancel := context.WithTimeout(context.Background(), connectTimeout)
	defer cancel()

	req.SetContext(ctx)

	err := req.Send()
	if err != nil {
		if req.IsErrorThrottle() {
			lib.Debugf("Firehose client %d: ERROR IsErrorThrottle: %s", clt.ID, err)
		} else {
			lib.Debugf("Firehose client %d: ERROR PutRecordBatch->Send: %s", clt.ID, err)
		}
		clt.srv.failure()
		return
	}

	lib.Debugf("Firehose: sent %d", len(records))

	for _, r := range clt.records {
		putPool(r)
	}
	clt.records = nil
}

// Exit finish the go routine of the client
func (clt *Client) Exit() {
	defer lib.Debugf("Firehose client %d: Exit, %d records lost", clt.ID, len(clt.records))

	clt.done <- true
	<-clt.finish

	clt.flush()
}
<commit_msg>Return to the pull with defer<commit_after>package fh

import (
	"context"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/firehose"
	"github.com/gallir/bytebufferpool"
	"github.com/gallir/smart-relayer/lib"
)

const (
	recordsTimeout = 15 * time.Second
	maxRecordSize  = 1000000 // The maximum size of a record sent to Kinesis Firehose, before base64-encoding, is 1000 KB
)

var (
	clientCount int64 = 0
	buffPool          = &bytebufferpool.Pool{}
)

// Client is the thread that connect to the remote redis server
type Client struct {
	sync.Mutex
	srv         *Server
	mode        int
	records     []*record
	status      int
	finish      chan bool
	done        chan bool
	ID          int
	tick        *time.Ticker
	lastFlushed time.Time
}

// NewClient creates a new client that connect to a Redis server
func NewClient(srv *Server) *Client {
	n := atomic.AddInt64(&clientCount, 1)

	clt := &Client{
		done:   make(chan bool),
		finish: make(chan bool),
		status: 0,
		srv:    srv,
		ID:     int(n),
		tick:   time.NewTicker(recordsTimeout),
	}

	if err := clt.Reload(); err != nil {
		lib.Debugf("Firehose client %d ERROR: reload %s", clt.ID, err)
		return nil
	}

	lib.Debugf("Firehose client %d ready", clt.ID)

	return clt
}

// Reload finish the listener and run it again
func (clt *Client) Reload() error {
	clt.Lock()
	defer clt.Unlock()

	if clt.status > 0 {
		clt.Exit()
	}

	go clt.listen()

	return nil
}

func (clt *Client) listen() {
	defer lib.Debugf("Firehose client %d: Closed listener", clt.ID)
	clt.status = 1

	for {
		select {
		case r := <-clt.srv.recordsCh:
			// ignore empty messages
			if r.len() <= 0 {
				continue
			}

			clt.records = append(clt.records, r)
			if len(clt.records) >= clt.srv.config.MaxRecords {
				clt.flush()
			}
		case <-clt.tick.C:
			if time.Since(clt.lastFlushed) >= recordsTimeout {
				clt.flush()
			}
		case <-clt.done:
			lib.Debugf("Firehose client %d: closing..", clt.ID)
			clt.finish <- true
			return
		}
	}

}

func (clt *Client) flush() {
	clt.lastFlushed = time.Now()

	if len(clt.records) <= 0 {
		return
	}

	records := make([]*firehose.Record, 0)

	b := buffPool.Get()
	defer buffPool.Put(b)

	for _, r := range clt.records {
		if r.len()+b.Len()+1 >= maxRecordSize {
			bs := append([]byte(nil), b.Bytes()...)
			b.Reset()
			records = append(records, &firehose.Record{Data: bs})
		}

		b.Write(r.bytes())
		b.Write([]byte("\n"))
	}

	if b.Len() > 0 {
		bs := append([]byte(nil), b.Bytes()...)
		records = append(records, &firehose.Record{Data: bs})
	}

	req, _ := clt.srv.awsSvc.PutRecordBatchRequest(&firehose.PutRecordBatchInput{
		DeliveryStreamName: aws.String(clt.srv.config.StreamName),
		Records:            records,
	})

	ctx, cancel := context.WithTimeout(context.Background(), connectTimeout)
	defer cancel()

	req.SetContext(ctx)

	err := req.Send()
	if err != nil {
		if req.IsErrorThrottle() {
			lib.Debugf("Firehose client %d: ERROR IsErrorThrottle: %s", clt.ID, err)
		} else {
			lib.Debugf("Firehose client %d: ERROR PutRecordBatch->Send: %s", clt.ID, err)
		}
		clt.srv.failure()
		return
	}

	lib.Debugf("Firehose: sent %d", len(records))

	for _, r := range clt.records {
		putPool(r)
	}
	clt.records = nil
}

// Exit finish the go routine of the client
func (clt *Client) Exit() {
	defer lib.Debugf("Firehose client %d: Exit, %d records lost", clt.ID, len(clt.records))

	clt.done <- true
	<-clt.finish

	clt.flush()
}
<|endoftext|>


<commit_before>package stringset_test

import (
	"fmt"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/jamesjoshuahill/stringset"
)

var _ = Describe("StringSet", func() {
	var set stringset.StringSet
	var other stringset.StringSet

	Context("when is has no members", func() {
		BeforeEach(func() {
			set = stringset.New()
		})

		AfterEach(func() {
			By("not changing the set")
			Expect(set).To(Equal(stringset.New()))
		})

		It("does not contain a member", func() {
			Expect(set.Contains("monkeys")).To(BeFalse())
		})

		It("lists no members", func() {
			Expect(set.Members()).To(BeEmpty())
		})

		It("prints neatly", func() {
			Expect(fmt.Sprintf("%v", set)).To(Equal(`{}`))
		})

		It("is empty", func() {
			Expect(set.Empty()).To(BeTrue())
		})

		It("has an order of 0", func() {
			Expect(set.Order()).To(Equal(0))
		})

		It("is a subset of itself", func() {
			Expect(set.IsSubset(set)).To(BeTrue())
		})

		It("is not a proper subset of itself", func() {
			Expect(set.IsProperSubset(set)).To(BeFalse())
		})

		It("is a superset of itself", func() {
			Expect(set.IsSuperset(set)).To(BeTrue())
		})
	})

	Context("when it has one member", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys")
		})

		AfterEach(func() {
			By("not changing the set")
			Expect(set).To(Equal(stringset.New("monkeys")))
		})

		It("contains the member", func() {
			Expect(set.Contains("monkeys")).To(BeTrue())
		})

		It("does not contain another member", func() {
			Expect(set.Contains("bananas")).To(BeFalse())
		})

		It("prints neatly", func() {
			Expect(fmt.Sprintf("%v", set)).To(Equal(`{monkeys}`))
		})

		It("is not empty", func() {
			Expect(set.Empty()).To(BeFalse())
		})

		It("has an order of one", func() {
			Expect(set.Order()).To(Equal(1))
		})

		It("is a subset of itself", func() {
			Expect(set.IsSubset(set)).To(BeTrue())
		})

		It("is not a proper subset of itself", func() {
			Expect(set.IsProperSubset(set)).To(BeFalse())
		})

		It("is a superset of itself", func() {
			Expect(set.IsSuperset(set)).To(BeTrue())
		})
	})

	Context("when it has some members", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas", "trees")
		})

		AfterEach(func() {
			By("not changing the set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("lists all members", func() {
			Expect(set.Members()).To(ContainElement("monkeys"))
			Expect(set.Members()).To(ContainElement("bananas"))
			Expect(set.Members()).To(ContainElement("trees"))
		})

		It("prints neatly", func() {
			Expect(fmt.Sprintf("%v", set)).To(Or(
				Equal(`{bananas monkeys trees}`),
				Equal(`{bananas trees monkeys}`),
				Equal(`{monkeys bananas trees}`),
				Equal(`{monkeys trees bananas}`),
				Equal(`{trees bananas monkeys}`),
				Equal(`{trees monkeys bananas}`),
			))
		})

		It("is not empty", func() {
			Expect(set.Empty()).To(BeFalse())
		})

		It("has an order of three", func() {
			Expect(set.Order()).To(Equal(3))
		})

		It("is a subset of itself", func() {
			Expect(set.IsSubset(set)).To(BeTrue())
		})

		It("is not a proper subset of itself", func() {
			Expect(set.IsProperSubset(set)).To(BeFalse())
		})

		It("is a superset of itself", func() {
			Expect(set.IsSuperset(set)).To(BeTrue())
		})
	})

	Context("when empty and the other set has a member", func() {
		BeforeEach(func() {
			set = stringset.New()
			other = stringset.New("trees")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New()))
			Expect(other).To(Equal(stringset.New("trees")))
		})

		It("subtracts nothing", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New()))
		})

		It("has no members in common", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New()))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(other))
		})

		It("includes the member in the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(other))
		})

		It("is a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeTrue())
		})

		It("is a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeTrue())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})

	Context("when it has members and the other set is a subset", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas")
			other = stringset.New("bananas")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas")))
			Expect(other).To(Equal(stringset.New("bananas")))
		})

		It("subtracts the member in common", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New("monkeys")))
		})

		It("intersects", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("bananas")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("subtracts the member in common from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("monkeys")))
		})

		It("is not a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeFalse())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeTrue())
		})
	})

	Context("when it has members and is a subset of the other set", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys")
			other = stringset.New("monkeys", "bananas")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys")))
			Expect(other).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("subtracts all members", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New()))
		})

		It("intersects", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("monkeys")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("subtracts the member in common from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("bananas")))
		})

		It("is a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeTrue())
		})

		It("is a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeTrue())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})

	Context("when it has members and equals the other set", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas", "trees")
			other = stringset.New("monkeys", "bananas", "trees")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas", "trees")))
			Expect(other).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("subtracts all members", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New()))
		})

		It("intersects all members", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("subtracts all members from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New()))
		})

		It("is a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeTrue())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeTrue())
		})
	})

	Context("when it has members and the other set intersects", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas")
			other = stringset.New("bananas", "trees")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas")))
			Expect(other).To(Equal(stringset.New("bananas", "trees")))
		})

		It("subtracts the member in common", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New("monkeys")))
		})

		It("intersects", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("bananas")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("subtracts the member in common from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("monkeys", "trees")))
		})

		It("is not a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeFalse())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})

	Context("when it has members and the other set does not intersect", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas")
			other = stringset.New("trees", "sunshine")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas")))
			Expect(other).To(Equal(stringset.New("trees", "sunshine")))
		})

		It("does not subtract any members", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("does not intersect", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New()))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas", "trees", "sunshine")))
		})

		It("includes all members in the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("monkeys", "bananas", "trees", "sunshine")))
		})

		It("is not a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeFalse())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})
})
<commit_msg>Polish tests<commit_after>package stringset_test

import (
	"fmt"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/jamesjoshuahill/stringset"
)

var _ = Describe("StringSet", func() {
	var set stringset.StringSet
	var other stringset.StringSet

	Context("when it has no members", func() {
		BeforeEach(func() {
			set = stringset.New()
		})

		AfterEach(func() {
			By("not changing the set")
			Expect(set).To(Equal(stringset.New()))
		})

		It("does not contain a member", func() {
			Expect(set.Contains("monkeys")).To(BeFalse())
		})

		It("lists no members", func() {
			Expect(set.Members()).To(BeEmpty())
		})

		It("prints neatly", func() {
			Expect(fmt.Sprintf("%v", set)).To(Equal(`{}`))
		})

		It("is empty", func() {
			Expect(set.Empty()).To(BeTrue())
		})

		It("has an order of 0", func() {
			Expect(set.Order()).To(Equal(0))
		})

		It("is a subset of itself", func() {
			Expect(set.IsSubset(set)).To(BeTrue())
		})

		It("is not a proper subset of itself", func() {
			Expect(set.IsProperSubset(set)).To(BeFalse())
		})

		It("is a superset of itself", func() {
			Expect(set.IsSuperset(set)).To(BeTrue())
		})
	})

	Context("when it has one member", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys")
		})

		AfterEach(func() {
			By("not changing the set")
			Expect(set).To(Equal(stringset.New("monkeys")))
		})

		It("contains the member", func() {
			Expect(set.Contains("monkeys")).To(BeTrue())
		})

		It("does not contain another member", func() {
			Expect(set.Contains("bananas")).To(BeFalse())
		})

		It("prints neatly", func() {
			Expect(fmt.Sprintf("%v", set)).To(Equal(`{monkeys}`))
		})

		It("is not empty", func() {
			Expect(set.Empty()).To(BeFalse())
		})

		It("has an order of one", func() {
			Expect(set.Order()).To(Equal(1))
		})

		It("is a subset of itself", func() {
			Expect(set.IsSubset(set)).To(BeTrue())
		})

		It("is not a proper subset of itself", func() {
			Expect(set.IsProperSubset(set)).To(BeFalse())
		})

		It("is a superset of itself", func() {
			Expect(set.IsSuperset(set)).To(BeTrue())
		})
	})

	Context("when it has some members", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas", "trees")
		})

		AfterEach(func() {
			By("not changing the set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("lists all members", func() {
			Expect(set.Members()).To(ContainElement("monkeys"))
			Expect(set.Members()).To(ContainElement("bananas"))
			Expect(set.Members()).To(ContainElement("trees"))
		})

		It("prints neatly", func() {
			Expect(fmt.Sprintf("%v", set)).To(Or(
				Equal(`{bananas monkeys trees}`),
				Equal(`{bananas trees monkeys}`),
				Equal(`{monkeys bananas trees}`),
				Equal(`{monkeys trees bananas}`),
				Equal(`{trees bananas monkeys}`),
				Equal(`{trees monkeys bananas}`),
			))
		})

		It("is not empty", func() {
			Expect(set.Empty()).To(BeFalse())
		})

		It("has an order of three", func() {
			Expect(set.Order()).To(Equal(3))
		})

		It("is a subset of itself", func() {
			Expect(set.IsSubset(set)).To(BeTrue())
		})

		It("is not a proper subset of itself", func() {
			Expect(set.IsProperSubset(set)).To(BeFalse())
		})

		It("is a superset of itself", func() {
			Expect(set.IsSuperset(set)).To(BeTrue())
		})
	})

	Context("when empty and the other set has a member", func() {
		BeforeEach(func() {
			set = stringset.New()
			other = stringset.New("trees")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New()))
			Expect(other).To(Equal(stringset.New("trees")))
		})

		It("subtracts nothing", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New()))
		})

		It("has no members in common", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New()))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("trees")))
		})

		It("includes the member in the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("trees")))
		})

		It("is a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeTrue())
		})

		It("is a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeTrue())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})

	Context("when it has members and the other set is a subset", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas")
			other = stringset.New("bananas")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas")))
			Expect(other).To(Equal(stringset.New("bananas")))
		})

		It("subtracts the member in common", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New("monkeys")))
		})

		It("intersects", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("bananas")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("subtracts the member in common from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("monkeys")))
		})

		It("is not a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeFalse())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeTrue())
		})
	})

	Context("when it has members and is a subset of the other set", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys")
			other = stringset.New("monkeys", "bananas")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys")))
			Expect(other).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("subtracts all members", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New()))
		})

		It("intersects", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("monkeys")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("subtracts the member in common from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("bananas")))
		})

		It("is a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeTrue())
		})

		It("is a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeTrue())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})

	Context("when it has members and equals the other set", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas", "trees")
			other = stringset.New("monkeys", "bananas", "trees")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas", "trees")))
			Expect(other).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("subtracts all members", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New()))
		})

		It("intersects all members", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("subtracts all members from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New()))
		})

		It("is a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeTrue())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeTrue())
		})
	})

	Context("when it has members and the other set intersects", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas")
			other = stringset.New("bananas", "trees")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas")))
			Expect(other).To(Equal(stringset.New("bananas", "trees")))
		})

		It("subtracts the member in common", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New("monkeys")))
		})

		It("intersects", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New("bananas")))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas", "trees")))
		})

		It("subtracts the member in common from the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("monkeys", "trees")))
		})

		It("is not a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeFalse())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})

	Context("when it has members and the other set does not intersect", func() {
		BeforeEach(func() {
			set = stringset.New("monkeys", "bananas")
			other = stringset.New("trees", "sunshine")
		})

		AfterEach(func() {
			By("not changing either set")
			Expect(set).To(Equal(stringset.New("monkeys", "bananas")))
			Expect(other).To(Equal(stringset.New("trees", "sunshine")))
		})

		It("does not subtract any members", func() {
			Expect(set.Subtract(other)).To(Equal(stringset.New("monkeys", "bananas")))
		})

		It("does not intersect", func() {
			Expect(set.Intersection(other)).To(Equal(stringset.New()))
		})

		It("includes all members in the union", func() {
			Expect(set.Union(other)).To(Equal(stringset.New("monkeys", "bananas", "trees", "sunshine")))
		})

		It("includes all members in the symmetric difference", func() {
			Expect(set.SymmetricDifference(other)).To(Equal(stringset.New("monkeys", "bananas", "trees", "sunshine")))
		})

		It("is not a subset of the other", func() {
			Expect(set.IsSubset(other)).To(BeFalse())
		})

		It("is not a proper subset the other", func() {
			Expect(set.IsProperSubset(other)).To(BeFalse())
		})

		It("is not a superset of the other", func() {
			Expect(set.IsSuperset(other)).To(BeFalse())
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package healthcheck

import (
	"fmt"
	"reflect"
	"time"

	"github.com/golang/glog"
	multierror "github.com/hashicorp/go-multierror"
	compute "google.golang.org/api/compute/v1"
	ingressbe "k8s.io/ingress-gce/pkg/backends"
	ingresshc "k8s.io/ingress-gce/pkg/healthchecks"

	utilsnamer "github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/namer"
)

const (
	// TODO(nikhiljindal): Share them with kubernetes/ingress.
	// These values set a low health threshold and a high failure threshold.
	// We're just trying to detect if the node networking is
	// borked, service level outages will get detected sooner
	// by kube-proxy.
	// DefaultHealthCheckInterval defines how frequently a probe runs
	DefaultHealthCheckInterval = 1 * time.Minute
	// DefaultHealthyThreshold defines the threshold of success probes that declare a backend "healthy"
	DefaultHealthyThreshold = 1
	// DefaultUnhealthyThreshold defines the threshold of failure probes that declare a backend "unhealthy"
	DefaultUnhealthyThreshold = 10
	// DefaultTimeout defines the timeout of each probe
	DefaultTimeout = 1 * time.Minute
)

// HealthCheckSyncer manages GCP health checks for multicluster GCP L7 load balancers.
type HealthCheckSyncer struct {
	namer *utilsnamer.Namer
	hcp   ingresshc.HealthCheckProvider
}

func NewHealthCheckSyncer(namer *utilsnamer.Namer, hcp ingresshc.HealthCheckProvider) HealthCheckSyncerInterface {
	return &HealthCheckSyncer{
		namer: namer,
		hcp:   hcp,
	}
}

// Ensure this implements HealthCheckSyncerInterface.
var _ HealthCheckSyncerInterface = &HealthCheckSyncer{}

// EnsureHealthCheck ensures that the required health check exists.
// Does nothing if it exists already, else creates a new one.
// Returns a map of the ensured health checks keyed by the corresponding port.
func (h *HealthCheckSyncer) EnsureHealthCheck(lbName string, ports []ingressbe.ServicePort, forceUpdate bool) (HealthChecksMap, error) {
	fmt.Println("Ensuring health checks")
	var err error
	ensuredHealthChecks := HealthChecksMap{}
	for _, p := range ports {
		hc, hcErr := h.ensureHealthCheck(lbName, p, forceUpdate)
		if hcErr != nil {
			hcErr = fmt.Errorf("Error %s in ensuring health check for port %v", hcErr, p)
			// Try ensuring health checks for all ports and return all errors at once.
			err = multierror.Append(err, hcErr)
			continue
		}
		ensuredHealthChecks[p.Port] = hc
	}
	return ensuredHealthChecks, err
}

func (h *HealthCheckSyncer) DeleteHealthChecks(ports []ingressbe.ServicePort) error {
	fmt.Println("Deleting health checks")
	var err error
	for _, p := range ports {
		if hcErr := h.deleteHealthCheck(p); hcErr != nil {
			err = multierror.Append(err, hcErr)
		}
	}
	if err != nil {
		fmt.Println("Errors in deleting health checks:", err)
		return err
	}
	fmt.Println("Successfully deleted all health checks")
	return nil
}

func (h *HealthCheckSyncer) deleteHealthCheck(port ingressbe.ServicePort) error {
	name := h.namer.HealthCheckName(port.Port)
	glog.V(2).Infof("Deleting health check %s", name)
	if err := h.hcp.DeleteHealthCheck(name); err != nil {
		glog.V(2).Infof("Error in deleting health check %s: %s", name, err)
		return err
	}
	glog.V(2).Infof("Successfully deleted health check %s", name)
	return nil
}

func (h *HealthCheckSyncer) ensureHealthCheck(lbName string, port ingressbe.ServicePort, forceUpdate bool) (*compute.HealthCheck, error) {
	fmt.Println("Ensuring health check for port:", port)
	desiredHC, err := h.desiredHealthCheck(lbName, port)
	if err != nil {
		return nil, fmt.Errorf("error %s in computing desired health check", err)
	}
	name := desiredHC.Name
	// Check if hc already exists.
	existingHC, err := h.hcp.GetHealthCheck(name)
	if err == nil {
		fmt.Println("Health check", name, "exists already. Checking if it matches our desired health check")
		glog.V(5).Infof("Existing health check: %+v\n, desired health check: %+v\n", existingHC, desiredHC)
		// Health check with that name exists already. Check if it matches what we want.
		if healthCheckMatches(&desiredHC, existingHC) {
			// Nothing to do. Desired health check exists already.
			fmt.Println("Desired health check exists already")
			return existingHC, nil
		}
		if forceUpdate {
			fmt.Println("Updating existing health check", name, "to match the desired state")
			return h.updateHealthCheck(&desiredHC)
		} else {
			// TODO(G-Harmon): prompt yes/no for overwriting.
			fmt.Println("Will not overwrite this differing health check without the --force flag.")
			glog.V(3).Infof("Existing check:\n%+v\nNew check:\n%+v\n", existingHC, desiredHC)
			return nil, fmt.Errorf("will not overwrite healthcheck without --force")
		}
	}
	glog.V(5).Infof("Got error %s while trying to get existing health check %s", err, name)
	// TODO(nikhiljindal): Handle non NotFound errors. We should create only if the error is NotFound.
	// Create the health check.
	return h.createHealthCheck(&desiredHC)
}

// updateHealthCheck updates the health check and returns the updated health check.
func (h *HealthCheckSyncer) updateHealthCheck(desiredHC *compute.HealthCheck) (*compute.HealthCheck, error) {
	name := desiredHC.Name
	fmt.Println("Updating existing health check", name, "to match the desired state")
	err := h.hcp.UpdateHealthCheck(desiredHC)
	if err != nil {
		return nil, err
	}
	fmt.Println("Health check", name, "updated successfully")
	return h.hcp.GetHealthCheck(name)
}

// createHealthCheck creates the health check and returns the created health check.
func (h *HealthCheckSyncer) createHealthCheck(desiredHC *compute.HealthCheck) (*compute.HealthCheck, error) {
	name := desiredHC.Name
	fmt.Println("Creating health check", name)
	glog.V(5).Infof("Creating health check %v", desiredHC)
	err := h.hcp.CreateHealthCheck(desiredHC)
	if err != nil {
		return nil, err
	}
	fmt.Println("Health check", name, "created successfully")
	return h.hcp.GetHealthCheck(name)
}

func healthCheckMatches(desiredHC, existingHC *compute.HealthCheck) bool {
	if desiredHC.CheckIntervalSec != existingHC.CheckIntervalSec ||
		// Ignore creationTimestamp.
		desiredHC.Description != existingHC.Description ||
		desiredHC.HealthyThreshold != existingHC.HealthyThreshold ||
		!reflect.DeepEqual(desiredHC.HttpHealthCheck, existingHC.HttpHealthCheck) ||
		!reflect.DeepEqual(desiredHC.HttpsHealthCheck, existingHC.HttpsHealthCheck) ||
		// Ignore id.
		desiredHC.Kind != existingHC.Kind ||
		desiredHC.Name != existingHC.Name ||
		// Ignore selfLink because it's not set in desiredHC.
		desiredHC.TimeoutSec != existingHC.TimeoutSec ||
		desiredHC.Type != existingHC.Type ||
		desiredHC.UnhealthyThreshold != existingHC.UnhealthyThreshold {
		glog.V(2).Infof("Health checks differ.")
		return false
	}
	glog.V(2).Infof("Health checks match.")
	return true
}

func (h *HealthCheckSyncer) desiredHealthCheck(lbName string, port ingressbe.ServicePort) (compute.HealthCheck, error) {
	// Compute the desired health check.
	hc := compute.HealthCheck{
		Name:        h.namer.HealthCheckName(port.Port),
		Description: fmt.Sprintf("Health check for service %s as part of kubernetes multicluster loadbalancer %s", port.Description(), lbName),
		// How often to health check.
		CheckIntervalSec: int64(DefaultHealthCheckInterval.Seconds()),
		// How long to wait before claiming failure of a health check.
		TimeoutSec: int64(DefaultTimeout.Seconds()),
		// Number of healthchecks to pass for a vm to be deemed healthy.
		HealthyThreshold: DefaultHealthyThreshold,
		// Number of healthchecks to fail before the vm is deemed unhealthy.
		UnhealthyThreshold: DefaultUnhealthyThreshold,
		Type:               string(port.Protocol),
		// TODO: Try Kind: compute#healthCheck
	}
	switch port.Protocol {
	case "HTTP":
		hc.HttpHealthCheck = &compute.HTTPHealthCheck{
			Port:        port.Port,
			RequestPath: "/healthz", // TODO(nikhiljindal): Allow customization.
		}
		break
	case "HTTPS":
		hc.HttpsHealthCheck = &compute.HTTPSHealthCheck{
			Port:        port.Port,  // TODO(nikhiljindal): Allow customization.
			RequestPath: "/healthz", // TODO(nikhiljindal): Allow customization.
		}
		break
	default:
		return compute.HealthCheck{}, fmt.Errorf("Unexpected port protocol: %s", port.Protocol)

	}
	return hc, nil
}
<commit_msg>Updating the default health check path<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package healthcheck

import (
	"fmt"
	"reflect"
	"time"

	"github.com/golang/glog"
	multierror "github.com/hashicorp/go-multierror"
	compute "google.golang.org/api/compute/v1"
	ingressbe "k8s.io/ingress-gce/pkg/backends"
	ingresshc "k8s.io/ingress-gce/pkg/healthchecks"

	utilsnamer "github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/namer"
)

const (
	// TODO(nikhiljindal): Share them with kubernetes/ingress.
	// These values set a low health threshold and a high failure threshold.
	// We're just trying to detect if the node networking is
	// borked, service level outages will get detected sooner
	// by kube-proxy.
	// DefaultHealthCheckInterval defines how frequently a probe runs
	DefaultHealthCheckInterval = 1 * time.Minute
	// DefaultHealthyThreshold defines the threshold of success probes that declare a backend "healthy"
	DefaultHealthyThreshold = 1
	// DefaultUnhealthyThreshold defines the threshold of failure probes that declare a backend "unhealthy"
	DefaultUnhealthyThreshold = 10
	// DefaultTimeout defines the timeout of each probe
	DefaultTimeout = 1 * time.Minute
)

// HealthCheckSyncer manages GCP health checks for multicluster GCP L7 load balancers.
type HealthCheckSyncer struct {
	namer *utilsnamer.Namer
	hcp   ingresshc.HealthCheckProvider
}

func NewHealthCheckSyncer(namer *utilsnamer.Namer, hcp ingresshc.HealthCheckProvider) HealthCheckSyncerInterface {
	return &HealthCheckSyncer{
		namer: namer,
		hcp:   hcp,
	}
}

// Ensure this implements HealthCheckSyncerInterface.
var _ HealthCheckSyncerInterface = &HealthCheckSyncer{}

// EnsureHealthCheck ensures that the required health check exists.
// Does nothing if it exists already, else creates a new one.
// Returns a map of the ensured health checks keyed by the corresponding port.
func (h *HealthCheckSyncer) EnsureHealthCheck(lbName string, ports []ingressbe.ServicePort, forceUpdate bool) (HealthChecksMap, error) {
	fmt.Println("Ensuring health checks")
	var err error
	ensuredHealthChecks := HealthChecksMap{}
	for _, p := range ports {
		hc, hcErr := h.ensureHealthCheck(lbName, p, forceUpdate)
		if hcErr != nil {
			hcErr = fmt.Errorf("Error %s in ensuring health check for port %v", hcErr, p)
			// Try ensuring health checks for all ports and return all errors at once.
			err = multierror.Append(err, hcErr)
			continue
		}
		ensuredHealthChecks[p.Port] = hc
	}
	return ensuredHealthChecks, err
}

func (h *HealthCheckSyncer) DeleteHealthChecks(ports []ingressbe.ServicePort) error {
	fmt.Println("Deleting health checks")
	var err error
	for _, p := range ports {
		if hcErr := h.deleteHealthCheck(p); hcErr != nil {
			err = multierror.Append(err, hcErr)
		}
	}
	if err != nil {
		fmt.Println("Errors in deleting health checks:", err)
		return err
	}
	fmt.Println("Successfully deleted all health checks")
	return nil
}

func (h *HealthCheckSyncer) deleteHealthCheck(port ingressbe.ServicePort) error {
	name := h.namer.HealthCheckName(port.Port)
	glog.V(2).Infof("Deleting health check %s", name)
	if err := h.hcp.DeleteHealthCheck(name); err != nil {
		glog.V(2).Infof("Error in deleting health check %s: %s", name, err)
		return err
	}
	glog.V(2).Infof("Successfully deleted health check %s", name)
	return nil
}

func (h *HealthCheckSyncer) ensureHealthCheck(lbName string, port ingressbe.ServicePort, forceUpdate bool) (*compute.HealthCheck, error) {
	fmt.Println("Ensuring health check for port:", port)
	desiredHC, err := h.desiredHealthCheck(lbName, port)
	if err != nil {
		return nil, fmt.Errorf("error %s in computing desired health check", err)
	}
	name := desiredHC.Name
	// Check if hc already exists.
	existingHC, err := h.hcp.GetHealthCheck(name)
	if err == nil {
		fmt.Println("Health check", name, "exists already. Checking if it matches our desired health check")
		glog.V(5).Infof("Existing health check: %+v\n, desired health check: %+v\n", existingHC, desiredHC)
		// Health check with that name exists already. Check if it matches what we want.
		if healthCheckMatches(&desiredHC, existingHC) {
			// Nothing to do. Desired health check exists already.
			fmt.Println("Desired health check exists already")
			return existingHC, nil
		}
		if forceUpdate {
			fmt.Println("Updating existing health check", name, "to match the desired state")
			return h.updateHealthCheck(&desiredHC)
		} else {
			// TODO(G-Harmon): prompt yes/no for overwriting.
			fmt.Println("Will not overwrite this differing health check without the --force flag.")
			glog.V(3).Infof("Existing check:\n%+v\nNew check:\n%+v\n", existingHC, desiredHC)
			return nil, fmt.Errorf("will not overwrite healthcheck without --force")
		}
	}
	glog.V(5).Infof("Got error %s while trying to get existing health check %s", err, name)
	// TODO(nikhiljindal): Handle non NotFound errors. We should create only if the error is NotFound.
	// Create the health check.
	return h.createHealthCheck(&desiredHC)
}

// updateHealthCheck updates the health check and returns the updated health check.
func (h *HealthCheckSyncer) updateHealthCheck(desiredHC *compute.HealthCheck) (*compute.HealthCheck, error) {
	name := desiredHC.Name
	fmt.Println("Updating existing health check", name, "to match the desired state")
	err := h.hcp.UpdateHealthCheck(desiredHC)
	if err != nil {
		return nil, err
	}
	fmt.Println("Health check", name, "updated successfully")
	return h.hcp.GetHealthCheck(name)
}

// createHealthCheck creates the health check and returns the created health check.
func (h *HealthCheckSyncer) createHealthCheck(desiredHC *compute.HealthCheck) (*compute.HealthCheck, error) {
	name := desiredHC.Name
	fmt.Println("Creating health check", name)
	glog.V(5).Infof("Creating health check %v", desiredHC)
	err := h.hcp.CreateHealthCheck(desiredHC)
	if err != nil {
		return nil, err
	}
	fmt.Println("Health check", name, "created successfully")
	return h.hcp.GetHealthCheck(name)
}

func healthCheckMatches(desiredHC, existingHC *compute.HealthCheck) bool {
	if desiredHC.CheckIntervalSec != existingHC.CheckIntervalSec ||
		// Ignore creationTimestamp.
		desiredHC.Description != existingHC.Description ||
		desiredHC.HealthyThreshold != existingHC.HealthyThreshold ||
		!reflect.DeepEqual(desiredHC.HttpHealthCheck, existingHC.HttpHealthCheck) ||
		!reflect.DeepEqual(desiredHC.HttpsHealthCheck, existingHC.HttpsHealthCheck) ||
		// Ignore id.
		desiredHC.Kind != existingHC.Kind ||
		desiredHC.Name != existingHC.Name ||
		// Ignore selfLink because it's not set in desiredHC.
		desiredHC.TimeoutSec != existingHC.TimeoutSec ||
		desiredHC.Type != existingHC.Type ||
		desiredHC.UnhealthyThreshold != existingHC.UnhealthyThreshold {
		glog.V(2).Infof("Health checks differ.")
		return false
	}
	glog.V(2).Infof("Health checks match.")
	return true
}

func (h *HealthCheckSyncer) desiredHealthCheck(lbName string, port ingressbe.ServicePort) (compute.HealthCheck, error) {
	// Compute the desired health check.
	hc := compute.HealthCheck{
		Name:        h.namer.HealthCheckName(port.Port),
		Description: fmt.Sprintf("Health check for service %s as part of kubernetes multicluster loadbalancer %s", port.Description(), lbName),
		// How often to health check.
		CheckIntervalSec: int64(DefaultHealthCheckInterval.Seconds()),
		// How long to wait before claiming failure of a health check.
		TimeoutSec: int64(DefaultTimeout.Seconds()),
		// Number of healthchecks to pass for a vm to be deemed healthy.
		HealthyThreshold: DefaultHealthyThreshold,
		// Number of healthchecks to fail before the vm is deemed unhealthy.
		UnhealthyThreshold: DefaultUnhealthyThreshold,
		Type:               string(port.Protocol),
		// TODO: Try Kind: compute#healthCheck
	}
	switch port.Protocol {
	case "HTTP":
		hc.HttpHealthCheck = &compute.HTTPHealthCheck{
			Port:        port.Port,
			RequestPath: "/", // TODO(nikhiljindal): Allow customization.
		}
		break
	case "HTTPS":
		hc.HttpsHealthCheck = &compute.HTTPSHealthCheck{
			Port:        port.Port, // TODO(nikhiljindal): Allow customization.
			RequestPath: "/",       // TODO(nikhiljindal): Allow customization.
		}
		break
	default:
		return compute.HealthCheck{}, fmt.Errorf("Unexpected port protocol: %s", port.Protocol)

	}
	return hc, nil
}
<|endoftext|>


<commit_before>package native

import (
	. "github.com/zxh0/jvm.go/jvmgo/any"
	"github.com/zxh0/jvm.go/jvmgo/jvm/rtda"
	rtc "github.com/zxh0/jvm.go/jvmgo/jvm/rtda/class"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/awt"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/io"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/lang"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/lang/reflect"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/net"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/security"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util/concurrent/atomic"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util/jar"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util/zip"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/awt"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/java2d/opengl"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/management"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/misc"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/reflect"
)

// register native methods
func init() {
	rtc.SetEmptyNativeMethod(emptyNativeMethod)
}

func emptyNativeMethod(frame *rtda.Frame) {
	// do nothing
}
<commit_msg>delete outdated comment<commit_after>package native

import (
	. "github.com/zxh0/jvm.go/jvmgo/any"
	"github.com/zxh0/jvm.go/jvmgo/jvm/rtda"
	rtc "github.com/zxh0/jvm.go/jvmgo/jvm/rtda/class"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/awt"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/io"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/lang"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/lang/reflect"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/net"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/security"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util/concurrent/atomic"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util/jar"
	_ "github.com/zxh0/jvm.go/jvmgo/native/java/util/zip"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/awt"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/java2d/opengl"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/management"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/misc"
	_ "github.com/zxh0/jvm.go/jvmgo/native/sun/reflect"
)

func init() {
	rtc.SetEmptyNativeMethod(emptyNativeMethod)
}

func emptyNativeMethod(frame *rtda.Frame) {
	// do nothing
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"net/url"
	"strings"
)

type canonical struct {
	h      http.Handler
	domain string
	code   int
}

// CanonicalHost is HTTP middleware that re-directs requests to the canonical
// domain. It accepts a domain and a status code (e.g. 301 or 302) and
// re-directs clients to this domain. The existing request path is maintained.
//
// Note: If the provided domain is considered invalid by url.Parse or otherwise
// returns an empty scheme or host, clients are not re-directed.
// not re-directed.
//
// Example:
//
//  r := mux.NewRouter()
//  canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
//  r.HandleFunc("/route", YourHandler)
//
//  log.Fatal(http.ListenAndServe(":7000", canonical(r)))
//
func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler {
	fn := func(h http.Handler) http.Handler {
		return canonical{h, domain, code}
	}

	return fn
}

func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	dest, err := url.Parse(c.domain)
	if err != nil {
		// Call the next handler if the provided domain fails to parse.
		c.h.ServeHTTP(w, r)
		return
	}

	if dest.Scheme == "" || dest.Host == "" {
		// Call the next handler if the scheme or host are empty.
		// Note that url.Parse won't fail on in this case.
		c.h.ServeHTTP(w, r)
		return
	}

	if !strings.EqualFold(cleanHost(r.Host), dest.Host) {
		// Re-build the destination URL
		dest := dest.Scheme + "://" + dest.Host + r.URL.Path
		http.Redirect(w, r, dest, c.code)
	}

	c.h.ServeHTTP(w, r)
}

// cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
// This is backported from Go 1.5 (in response to issue #11206) and attempts to
// mitigate malformed Host headers that do not match the format in RFC7230.
func cleanHost(in string) string {
	if i := strings.IndexAny(in, " /"); i != -1 {
		return in[:i]
	}
	return in
}
<commit_msg>fix "http: multiple response.WriteHeader calls" in CanonicalHost<commit_after>package handlers

import (
	"net/http"
	"net/url"
	"strings"
)

type canonical struct {
	h      http.Handler
	domain string
	code   int
}

// CanonicalHost is HTTP middleware that re-directs requests to the canonical
// domain. It accepts a domain and a status code (e.g. 301 or 302) and
// re-directs clients to this domain. The existing request path is maintained.
//
// Note: If the provided domain is considered invalid by url.Parse or otherwise
// returns an empty scheme or host, clients are not re-directed.
// not re-directed.
//
// Example:
//
//  r := mux.NewRouter()
//  canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
//  r.HandleFunc("/route", YourHandler)
//
//  log.Fatal(http.ListenAndServe(":7000", canonical(r)))
//
func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler {
	fn := func(h http.Handler) http.Handler {
		return canonical{h, domain, code}
	}

	return fn
}

func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	dest, err := url.Parse(c.domain)
	if err != nil {
		// Call the next handler if the provided domain fails to parse.
		c.h.ServeHTTP(w, r)
		return
	}

	if dest.Scheme == "" || dest.Host == "" {
		// Call the next handler if the scheme or host are empty.
		// Note that url.Parse won't fail on in this case.
		c.h.ServeHTTP(w, r)
		return
	}

	if !strings.EqualFold(cleanHost(r.Host), dest.Host) {
		// Re-build the destination URL
		dest := dest.Scheme + "://" + dest.Host + r.URL.Path
		http.Redirect(w, r, dest, c.code)
		return
	}

	c.h.ServeHTTP(w, r)
}

// cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
// This is backported from Go 1.5 (in response to issue #11206) and attempts to
// mitigate malformed Host headers that do not match the format in RFC7230.
func cleanHost(in string) string {
	if i := strings.IndexAny(in, " /"); i != -1 {
		return in[:i]
	}
	return in
}
<|endoftext|>


<commit_before>// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing

import (
	"errors"
	"fmt"
	"github.com/globocom/tsuru/provision"
	"io"
	"strconv"
	"sync"
	"time"
)

func init() {
	provision.Register("fake", &FakeProvisioner{})
}

// Fake implementation for provision.Unit.
type FakeUnit struct {
	name    string
	machine int
	status  provision.Status
	actions []string
}

func (u *FakeUnit) GetName() string {
	u.actions = append(u.actions, "getname")
	return u.name
}

func (u *FakeUnit) GetMachine() int {
	u.actions = append(u.actions, "getmachine")
	return u.machine
}

func (u *FakeUnit) GetStatus() provision.Status {
	return u.status
}

// Fake implementation for provision.App.
type FakeApp struct {
	name      string
	framework string
	units     []provision.AppUnit
	logs      []string
	actions   []string
}

func NewFakeApp(name, framework string, units int) *FakeApp {
	app := FakeApp{
		name:      name,
		framework: framework,
		units:     make([]provision.AppUnit, units),
	}
	for i := 0; i < units; i++ {
		app.units[i] = &FakeUnit{name: name, machine: i + 1}
	}
	return &app
}

func (a *FakeApp) Log(message, source string) error {
	a.logs = append(a.logs, source+message)
	a.actions = append(a.actions, "log "+source+" - "+message)
	return nil
}

func (a *FakeApp) GetName() string {
	a.actions = append(a.actions, "getname")
	return a.name
}

func (a *FakeApp) GetFramework() string {
	a.actions = append(a.actions, "getframework")
	return a.framework
}

func (a *FakeApp) ProvisionUnits() []provision.AppUnit {
	a.actions = append(a.actions, "getunits")
	return a.units
}

type Cmd struct {
	Cmd  string
	Args []string
	App  provision.App
}

type failure struct {
	method string
	err    error
}

// Fake implementation for provision.Provisioner.
type FakeProvisioner struct {
	apps     []provision.App
	units    map[string][]provision.Unit
	cmds     []Cmd
	outputs  chan []byte
	failures chan failure
	mut      sync.Mutex
}

func NewFakeProvisioner() *FakeProvisioner {
	p := FakeProvisioner{}
	p.outputs = make(chan []byte, 8)
	p.failures = make(chan failure, 8)
	p.units = make(map[string][]provision.Unit)
	return &p
}

func (p *FakeProvisioner) getError(method string) error {
	select {
	case fail := <-p.failures:
		if fail.method == method {
			return fail.err
		}
		p.failures <- fail
	case <-time.After(1e6):
	}
	return nil
}

// GetCmds returns a list of commands executed in an app. If you don't specify
// the command (""), it will return all commands executed in the given app.
func (p *FakeProvisioner) GetCmds(cmd string, app provision.App) []Cmd {
	var cmds []Cmd
	p.mut.Lock()
	for _, c := range p.cmds {
		if (cmd == "" || c.Cmd == cmd) && app.GetName() == c.App.GetName() {
			cmds = append(cmds, c)
		}
	}
	p.mut.Unlock()
	return cmds
}

func (p *FakeProvisioner) FindApp(app provision.App) int {
	for i, a := range p.apps {
		if a.GetName() == app.GetName() {
			return i
		}
	}
	return -1
}

func (p *FakeProvisioner) GetUnits(app provision.App) []provision.Unit {
	return p.units[app.GetName()]
}

func (p *FakeProvisioner) PrepareOutput(b []byte) {
	p.outputs <- b
}

func (p *FakeProvisioner) PrepareFailure(method string, err error) {
	p.failures <- failure{method, err}
}

func (p *FakeProvisioner) Reset() {
	close(p.outputs)
	close(p.failures)
	p.outputs = make(chan []byte, 8)
	p.failures = make(chan failure, 8)
	p.units = make(map[string][]provision.Unit)
	p.cmds = nil
}

func (p *FakeProvisioner) Provision(app provision.App) error {
	if err := p.getError("Provision"); err != nil {
		return err
	}
	index := p.FindApp(app)
	if index > -1 {
		return &provision.Error{Reason: "App already provisioned."}
	}
	p.apps = append(p.apps, app)
	p.units[app.GetName()] = nil
	return nil
}

func (p *FakeProvisioner) Destroy(app provision.App) error {
	if err := p.getError("Destroy"); err != nil {
		return err
	}
	index := p.FindApp(app)
	if index == -1 {
		return &provision.Error{Reason: "App is not provisioned."}
	}
	copy(p.apps[index:], p.apps[index+1:])
	p.apps = p.apps[:len(p.apps)-1]
	return nil
}

func (p *FakeProvisioner) AddUnits(app provision.App, n uint) ([]provision.Unit, error) {
	if err := p.getError("AddUnits"); err != nil {
		return nil, err
	}
	if n == 0 {
		return nil, errors.New("Cannot add 0 units.")
	}
	index := p.FindApp(app)
	if index < 0 {
		return nil, errors.New("App is not provisioned.")
	}
	name := app.GetName()
	framework := app.GetFramework()
	length := uint(len(p.units[name]))
	for i := uint(0); i < n; i++ {
		unit := provision.Unit{
			Name:    fmt.Sprintf("%s/%d", name, length+i),
			AppName: name,
			Type:    framework,
			Status:  provision.StatusStarted,
			Ip:      fmt.Sprintf("10.10.10.%d", length+i),
			Machine: int(length + i),
		}
		p.units[name] = append(p.units[name], unit)
	}
	return p.units[name][length:], nil
}

func (p *FakeProvisioner) RemoveUnit(app provision.App, name string) error {
	return nil
}

func (p *FakeProvisioner) RemoveUnits(app provision.App, n uint) error {
	return nil
}

func (p *FakeProvisioner) ExecuteCommand(stdout, stderr io.Writer, app provision.App, cmd string, args ...string) error {
	var (
		output []byte
		err    error
	)
	command := Cmd{
		Cmd:  cmd,
		Args: args,
		App:  app,
	}
	p.mut.Lock()
	p.cmds = append(p.cmds, command)
	p.mut.Unlock()
	select {
	case output = <-p.outputs:
		select {
		case fail := <-p.failures:
			if fail.method == "ExecuteCommand" {
				stderr.Write(output)
				return fail.err
			} else {
				p.failures <- fail
			}
		case <-time.After(1e6):
			stdout.Write(output)
		}
	case fail := <-p.failures:
		if fail.method == "ExecuteCommand" {
			err = fail.err
			select {
			case output = <-p.outputs:
				stderr.Write(output)
			case <-time.After(1e6):
			}
		} else {
			p.failures <- fail
		}
	case <-time.After(2e9):
		return errors.New("FakeProvisioner timed out waiting for output.")
	}
	return err
}

func (p *FakeProvisioner) CollectStatus() ([]provision.Unit, error) {
	if err := p.getError("CollectStatus"); err != nil {
		return nil, err
	}
	units := make([]provision.Unit, len(p.apps))
	for i, app := range p.apps {
		unit := provision.Unit{
			Name:    app.GetName() + "/0",
			AppName: app.GetName(),
			Type:    app.GetFramework(),
			Status:  "started",
			Ip:      "10.10.10." + strconv.Itoa(i+1),
			Machine: i + 1,
		}
		units[i] = unit
	}
	return units, nil
}
<commit_msg>testing: fix more racy code in FakeProvisioner<commit_after>// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing

import (
	"errors"
	"fmt"
	"github.com/globocom/tsuru/provision"
	"io"
	"strconv"
	"sync"
	"time"
)

func init() {
	provision.Register("fake", &FakeProvisioner{})
}

// Fake implementation for provision.Unit.
type FakeUnit struct {
	name    string
	machine int
	status  provision.Status
	actions []string
}

func (u *FakeUnit) GetName() string {
	u.actions = append(u.actions, "getname")
	return u.name
}

func (u *FakeUnit) GetMachine() int {
	u.actions = append(u.actions, "getmachine")
	return u.machine
}

func (u *FakeUnit) GetStatus() provision.Status {
	return u.status
}

// Fake implementation for provision.App.
type FakeApp struct {
	name      string
	framework string
	units     []provision.AppUnit
	logs      []string
	actions   []string
}

func NewFakeApp(name, framework string, units int) *FakeApp {
	app := FakeApp{
		name:      name,
		framework: framework,
		units:     make([]provision.AppUnit, units),
	}
	for i := 0; i < units; i++ {
		app.units[i] = &FakeUnit{name: name, machine: i + 1}
	}
	return &app
}

func (a *FakeApp) Log(message, source string) error {
	a.logs = append(a.logs, source+message)
	a.actions = append(a.actions, "log "+source+" - "+message)
	return nil
}

func (a *FakeApp) GetName() string {
	a.actions = append(a.actions, "getname")
	return a.name
}

func (a *FakeApp) GetFramework() string {
	a.actions = append(a.actions, "getframework")
	return a.framework
}

func (a *FakeApp) ProvisionUnits() []provision.AppUnit {
	a.actions = append(a.actions, "getunits")
	return a.units
}

type Cmd struct {
	Cmd  string
	Args []string
	App  provision.App
}

type failure struct {
	method string
	err    error
}

// Fake implementation for provision.Provisioner.
type FakeProvisioner struct {
	apps     []provision.App
	units    map[string][]provision.Unit
	cmds     []Cmd
	outputs  chan []byte
	failures chan failure
	cmdMut   sync.Mutex
	unitMut  sync.Mutex
}

func NewFakeProvisioner() *FakeProvisioner {
	p := FakeProvisioner{}
	p.outputs = make(chan []byte, 8)
	p.failures = make(chan failure, 8)
	p.units = make(map[string][]provision.Unit)
	return &p
}

func (p *FakeProvisioner) getError(method string) error {
	select {
	case fail := <-p.failures:
		if fail.method == method {
			return fail.err
		}
		p.failures <- fail
	case <-time.After(1e6):
	}
	return nil
}

// GetCmds returns a list of commands executed in an app. If you don't specify
// the command (""), it will return all commands executed in the given app.
func (p *FakeProvisioner) GetCmds(cmd string, app provision.App) []Cmd {
	var cmds []Cmd
	p.cmdMut.Lock()
	for _, c := range p.cmds {
		if (cmd == "" || c.Cmd == cmd) && app.GetName() == c.App.GetName() {
			cmds = append(cmds, c)
		}
	}
	p.cmdMut.Unlock()
	return cmds
}

func (p *FakeProvisioner) FindApp(app provision.App) int {
	for i, a := range p.apps {
		if a.GetName() == app.GetName() {
			return i
		}
	}
	return -1
}

func (p *FakeProvisioner) GetUnits(app provision.App) []provision.Unit {
	p.unitMut.Lock()
	defer p.unitMut.Unlock()
	return p.units[app.GetName()]
}

func (p *FakeProvisioner) PrepareOutput(b []byte) {
	p.outputs <- b
}

func (p *FakeProvisioner) PrepareFailure(method string, err error) {
	p.failures <- failure{method, err}
}

func (p *FakeProvisioner) Reset() {
	p.unitMut.Lock()
	p.units = make(map[string][]provision.Unit)
	p.unitMut.Unlock()

	p.cmdMut.Lock()
	p.cmds = nil
	p.cmdMut.Unlock()

	for {
		select {
		case <-p.outputs:
		case <-p.failures:
		default:
			return
		}
	}
}

func (p *FakeProvisioner) Provision(app provision.App) error {
	if err := p.getError("Provision"); err != nil {
		return err
	}
	index := p.FindApp(app)
	if index > -1 {
		return &provision.Error{Reason: "App already provisioned."}
	}
	p.apps = append(p.apps, app)
	p.unitMut.Lock()
	p.units[app.GetName()] = nil
	p.unitMut.Unlock()
	return nil
}

func (p *FakeProvisioner) Destroy(app provision.App) error {
	if err := p.getError("Destroy"); err != nil {
		return err
	}
	index := p.FindApp(app)
	if index == -1 {
		return &provision.Error{Reason: "App is not provisioned."}
	}
	copy(p.apps[index:], p.apps[index+1:])
	p.apps = p.apps[:len(p.apps)-1]
	p.unitMut.Lock()
	delete(p.units, app.GetName())
	p.unitMut.Unlock()
	return nil
}

func (p *FakeProvisioner) AddUnits(app provision.App, n uint) ([]provision.Unit, error) {
	if err := p.getError("AddUnits"); err != nil {
		return nil, err
	}
	if n == 0 {
		return nil, errors.New("Cannot add 0 units.")
	}
	index := p.FindApp(app)
	if index < 0 {
		return nil, errors.New("App is not provisioned.")
	}
	name := app.GetName()
	framework := app.GetFramework()
	p.unitMut.Lock()
	defer p.unitMut.Unlock()
	length := uint(len(p.units[name]))
	for i := uint(0); i < n; i++ {
		unit := provision.Unit{
			Name:    fmt.Sprintf("%s/%d", name, length+i),
			AppName: name,
			Type:    framework,
			Status:  provision.StatusStarted,
			Ip:      fmt.Sprintf("10.10.10.%d", length+i),
			Machine: int(length + i),
		}
		p.units[name] = append(p.units[name], unit)
	}
	return p.units[name][length:], nil
}

func (p *FakeProvisioner) RemoveUnit(app provision.App, name string) error {
	return nil
}

func (p *FakeProvisioner) RemoveUnits(app provision.App, n uint) error {
	return nil
}

func (p *FakeProvisioner) ExecuteCommand(stdout, stderr io.Writer, app provision.App, cmd string, args ...string) error {
	var (
		output []byte
		err    error
	)
	command := Cmd{
		Cmd:  cmd,
		Args: args,
		App:  app,
	}
	p.cmdMut.Lock()
	p.cmds = append(p.cmds, command)
	p.cmdMut.Unlock()
	select {
	case output = <-p.outputs:
		select {
		case fail := <-p.failures:
			if fail.method == "ExecuteCommand" {
				stderr.Write(output)
				return fail.err
			} else {
				p.failures <- fail
			}
		case <-time.After(1e6):
			stdout.Write(output)
		}
	case fail := <-p.failures:
		if fail.method == "ExecuteCommand" {
			err = fail.err
			select {
			case output = <-p.outputs:
				stderr.Write(output)
			case <-time.After(1e6):
			}
		} else {
			p.failures <- fail
		}
	case <-time.After(2e9):
		return errors.New("FakeProvisioner timed out waiting for output.")
	}
	return err
}

func (p *FakeProvisioner) CollectStatus() ([]provision.Unit, error) {
	if err := p.getError("CollectStatus"); err != nil {
		return nil, err
	}
	units := make([]provision.Unit, len(p.apps))
	for i, app := range p.apps {
		unit := provision.Unit{
			Name:    app.GetName() + "/0",
			AppName: app.GetName(),
			Type:    app.GetFramework(),
			Status:  "started",
			Ip:      "10.10.10." + strconv.Itoa(i+1),
			Machine: i + 1,
		}
		units[i] = unit
	}
	return units, nil
}
<|endoftext|>