<commit_before>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package router

import (
	"sync"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"

	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/snow"
	"github.com/ava-labs/avalanchego/snow/engine/common"
	"github.com/ava-labs/avalanchego/snow/networking/benchlist"
	"github.com/ava-labs/avalanchego/snow/networking/timeout"
	"github.com/ava-labs/avalanchego/snow/validators"
	"github.com/ava-labs/avalanchego/utils/constants"
	"github.com/ava-labs/avalanchego/utils/logging"
	"github.com/ava-labs/avalanchego/utils/timer"
)

func TestShutdown(t *testing.T) {
	vdrs := validators.NewSet()
	err := vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	benchlist := benchlist.NewNoBenchlist()
	tm := timeout.Manager{}
	err = tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     time.Millisecond,
			MinimumTimeout:     time.Millisecond,
			MaximumTimeout:     10 * time.Second,
			TimeoutCoefficient: 1.25,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist,
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Second, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	engine := common.EngineTest{T: t}
	engine.Default(false)

	shutdownCalled := make(chan struct{}, 1)

	engine.ContextF = snow.DefaultContextTest
	engine.ShutdownF = func() error { shutdownCalled <- struct{}{}; return nil }

	handler := &Handler{}
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	go handler.Dispatch()

	chainRouter.AddChain(handler)

	chainRouter.Shutdown()

	ticker := time.NewTicker(250 * time.Millisecond)
	select {
	case <-ticker.C:
		t.Fatalf("Handler shutdown was not called or timed out after 250ms during chainRouter shutdown")
	case <-shutdownCalled:
	}

	select {
	case <-handler.closed:
	default:
		t.Fatal("handler shutdown but never closed its closing channel")
	}
}

func TestShutdownTimesOut(t *testing.T) {
	vdrs := validators.NewSet()
	err := vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	benchlist := benchlist.NewNoBenchlist()
	tm := timeout.Manager{}
	// Ensure that the MultiPut request does not timeout
	err = tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     time.Second,
			MinimumTimeout:     500 * time.Millisecond,
			MaximumTimeout:     10 * time.Second,
			TimeoutCoefficient: 1.25,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist,
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Millisecond, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	engine := common.EngineTest{T: t}
	engine.Default(false)

	engineFinished := make(chan struct{}, 1)

	// MultiPut blocks for two seconds
	engine.MultiPutF = func(validatorID ids.ShortID, requestID uint32, containers [][]byte) error {
		time.Sleep(2 * time.Second)
		engineFinished <- struct{}{}
		return nil
	}

	closed := new(int)

	engine.ContextF = snow.DefaultContextTest
	engine.ShutdownF = func() error { *closed++; return nil }

	handler := &Handler{}
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	chainRouter.AddChain(handler)

	go handler.Dispatch()

	shutdownFinished := make(chan struct{}, 1)

	go func() {
		// TODO put function below
		handler.MultiPut(ids.ShortID{}, 1, nil, func() {})
		time.Sleep(50 * time.Millisecond) // Pause to ensure message gets processed

		chainRouter.Shutdown()
		shutdownFinished <- struct{}{}
	}()

	select {
	case <-engineFinished:
		t.Fatalf("Shutdown should have finished in one millisecond before timing out instead of waiting for engine to finish shutting down.")
	case <-shutdownFinished:
	}
}

// Ensure that a timeout fires if we don't get a response to a request
func TestRouterTimeout(t *testing.T) {
	// Create a timeout manager
	maxTimeout := 25 * time.Millisecond
	tm := timeout.Manager{}
	err := tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     10 * time.Millisecond,
			MinimumTimeout:     10 * time.Millisecond,
			MaximumTimeout:     maxTimeout,
			TimeoutCoefficient: 1,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist.NewNoBenchlist(),
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	// Create a router
	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Millisecond, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	// Create an engine and handler
	engine := common.EngineTest{T: t}
	engine.Default(false)

	var (
		calledGetFailed, calledGetAncestorsFailed,
		calledQueryFailed, calledQueryFailed2,
		calledGetAcceptedFailed, calledGetAcceptedFrontierFailed bool

		wg = sync.WaitGroup{}
	)

	engine.GetFailedF = func(validatorID ids.ShortID, requestID uint32) error { wg.Done(); calledGetFailed = true; return nil }
	engine.GetAncestorsFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		calledGetAncestorsFailed = true
		return nil
	}
	engine.QueryFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		if !calledQueryFailed {
			calledQueryFailed = true
			return nil
		}
		calledQueryFailed2 = true
		return nil
	}
	engine.GetAcceptedFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		calledGetAcceptedFailed = true
		return nil
	}
	engine.GetAcceptedFrontierFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		calledGetAcceptedFrontierFailed = true
		return nil
	}

	engine.ContextF = snow.DefaultContextTest

	handler := &Handler{}
	vdrs := validators.NewSet()
	err = vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	chainRouter.AddChain(handler)
	go handler.Dispatch()

	// Register requests for each request type
	msgs := []constants.MsgType{
		constants.GetMsg,
		constants.GetAncestorsMsg,
		constants.PullQueryMsg,
		constants.PushQueryMsg,
		constants.GetAcceptedMsg,
		constants.GetAcceptedFrontierMsg,
	}

	wg.Add(len(msgs))

	for i, msg := range msgs {
		chainRouter.RegisterRequest(ids.GenerateTestShortID(), handler.ctx.ChainID, uint32(i), msg)
	}

	wg.Wait()
	chainRouter.lock.Lock()
	defer chainRouter.lock.Unlock()
	assert.True(t, calledGetFailed && calledGetAncestorsFailed && calledQueryFailed2 && calledGetAcceptedFailed && calledGetAcceptedFrontierFailed)
}

func TestRouterClearTimeouts(t *testing.T) {
	// Create a timeout manager
	tm := timeout.Manager{}
	err := tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     3 * time.Second,
			MinimumTimeout:     3 * time.Second,
			MaximumTimeout:     5 * time.Minute,
			TimeoutCoefficient: 1,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist.NewNoBenchlist(),
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	// Create a router
	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Millisecond, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	// Create an engine and handler
	engine := common.EngineTest{T: t}
	engine.Default(false)

	engine.ContextF = snow.DefaultContextTest

	vdrs := validators.NewSet()
	err = vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	handler := &Handler{}
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	chainRouter.AddChain(handler)
	go handler.Dispatch()

	// Register requests for each request type
	msgs := []constants.MsgType{
		constants.GetMsg,
		constants.GetAncestorsMsg,
		constants.PullQueryMsg,
		constants.PushQueryMsg,
		constants.GetAcceptedMsg,
		constants.GetAcceptedFrontierMsg,
	}

	vID := ids.GenerateTestShortID()
	for i, msg := range msgs {
		chainRouter.RegisterRequest(vID, handler.ctx.ChainID, uint32(i), msg)
	}

	// Clear each timeout by simulating responses to the queries
	// Note: Depends on the ordering of [msgs]
	chainRouter.Put(vID, handler.ctx.ChainID, 0, ids.GenerateTestID(), nil, nil)
	chainRouter.MultiPut(vID, handler.ctx.ChainID, 1, nil, nil)
	chainRouter.Chits(vID, handler.ctx.ChainID, 2, nil, nil)
	chainRouter.Chits(vID, handler.ctx.ChainID, 3, nil, nil)
	chainRouter.Accepted(vID, handler.ctx.ChainID, 4, nil, nil)
	chainRouter.AcceptedFrontier(vID, handler.ctx.ChainID, 5, nil, nil)

	assert.Equal(t, chainRouter.timedRequests.Len(), 0)
}
<commit_msg>remove old TODO<commit_after>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package router

import (
	"sync"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"

	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/snow"
	"github.com/ava-labs/avalanchego/snow/engine/common"
	"github.com/ava-labs/avalanchego/snow/networking/benchlist"
	"github.com/ava-labs/avalanchego/snow/networking/timeout"
	"github.com/ava-labs/avalanchego/snow/validators"
	"github.com/ava-labs/avalanchego/utils/constants"
	"github.com/ava-labs/avalanchego/utils/logging"
	"github.com/ava-labs/avalanchego/utils/timer"
)

func TestShutdown(t *testing.T) {
	vdrs := validators.NewSet()
	err := vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	benchlist := benchlist.NewNoBenchlist()
	tm := timeout.Manager{}
	err = tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     time.Millisecond,
			MinimumTimeout:     time.Millisecond,
			MaximumTimeout:     10 * time.Second,
			TimeoutCoefficient: 1.25,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist,
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Second, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	engine := common.EngineTest{T: t}
	engine.Default(false)

	shutdownCalled := make(chan struct{}, 1)

	engine.ContextF = snow.DefaultContextTest
	engine.ShutdownF = func() error { shutdownCalled <- struct{}{}; return nil }

	handler := &Handler{}
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	go handler.Dispatch()

	chainRouter.AddChain(handler)

	chainRouter.Shutdown()

	ticker := time.NewTicker(250 * time.Millisecond)
	select {
	case <-ticker.C:
		t.Fatalf("Handler shutdown was not called or timed out after 250ms during chainRouter shutdown")
	case <-shutdownCalled:
	}

	select {
	case <-handler.closed:
	default:
		t.Fatal("handler shutdown but never closed its closing channel")
	}
}

func TestShutdownTimesOut(t *testing.T) {
	vdrs := validators.NewSet()
	err := vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	benchlist := benchlist.NewNoBenchlist()
	tm := timeout.Manager{}
	// Ensure that the MultiPut request does not timeout
	err = tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     time.Second,
			MinimumTimeout:     500 * time.Millisecond,
			MaximumTimeout:     10 * time.Second,
			TimeoutCoefficient: 1.25,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist,
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Millisecond, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	engine := common.EngineTest{T: t}
	engine.Default(false)

	engineFinished := make(chan struct{}, 1)

	// MultiPut blocks for two seconds
	engine.MultiPutF = func(validatorID ids.ShortID, requestID uint32, containers [][]byte) error {
		time.Sleep(2 * time.Second)
		engineFinished <- struct{}{}
		return nil
	}

	closed := new(int)

	engine.ContextF = snow.DefaultContextTest
	engine.ShutdownF = func() error { *closed++; return nil }

	handler := &Handler{}
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	chainRouter.AddChain(handler)

	go handler.Dispatch()

	shutdownFinished := make(chan struct{}, 1)

	go func() {
		handler.MultiPut(ids.ShortID{}, 1, nil, func() {})
		time.Sleep(50 * time.Millisecond) // Pause to ensure message gets processed

		chainRouter.Shutdown()
		shutdownFinished <- struct{}{}
	}()

	select {
	case <-engineFinished:
		t.Fatalf("Shutdown should have finished in one millisecond before timing out instead of waiting for engine to finish shutting down.")
	case <-shutdownFinished:
	}
}

// Ensure that a timeout fires if we don't get a response to a request
func TestRouterTimeout(t *testing.T) {
	// Create a timeout manager
	maxTimeout := 25 * time.Millisecond
	tm := timeout.Manager{}
	err := tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     10 * time.Millisecond,
			MinimumTimeout:     10 * time.Millisecond,
			MaximumTimeout:     maxTimeout,
			TimeoutCoefficient: 1,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist.NewNoBenchlist(),
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	// Create a router
	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Millisecond, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	// Create an engine and handler
	engine := common.EngineTest{T: t}
	engine.Default(false)

	var (
		calledGetFailed, calledGetAncestorsFailed,
		calledQueryFailed, calledQueryFailed2,
		calledGetAcceptedFailed, calledGetAcceptedFrontierFailed bool

		wg = sync.WaitGroup{}
	)

	engine.GetFailedF = func(validatorID ids.ShortID, requestID uint32) error { wg.Done(); calledGetFailed = true; return nil }
	engine.GetAncestorsFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		calledGetAncestorsFailed = true
		return nil
	}
	engine.QueryFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		if !calledQueryFailed {
			calledQueryFailed = true
			return nil
		}
		calledQueryFailed2 = true
		return nil
	}
	engine.GetAcceptedFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		calledGetAcceptedFailed = true
		return nil
	}
	engine.GetAcceptedFrontierFailedF = func(validatorID ids.ShortID, requestID uint32) error {
		defer wg.Done()
		calledGetAcceptedFrontierFailed = true
		return nil
	}

	engine.ContextF = snow.DefaultContextTest

	handler := &Handler{}
	vdrs := validators.NewSet()
	err = vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	chainRouter.AddChain(handler)
	go handler.Dispatch()

	// Register requests for each request type
	msgs := []constants.MsgType{
		constants.GetMsg,
		constants.GetAncestorsMsg,
		constants.PullQueryMsg,
		constants.PushQueryMsg,
		constants.GetAcceptedMsg,
		constants.GetAcceptedFrontierMsg,
	}

	wg.Add(len(msgs))

	for i, msg := range msgs {
		chainRouter.RegisterRequest(ids.GenerateTestShortID(), handler.ctx.ChainID, uint32(i), msg)
	}

	wg.Wait()
	chainRouter.lock.Lock()
	defer chainRouter.lock.Unlock()
	assert.True(t, calledGetFailed && calledGetAncestorsFailed && calledQueryFailed2 && calledGetAcceptedFailed && calledGetAcceptedFrontierFailed)
}

func TestRouterClearTimeouts(t *testing.T) {
	// Create a timeout manager
	tm := timeout.Manager{}
	err := tm.Initialize(
		&timer.AdaptiveTimeoutConfig{
			InitialTimeout:     3 * time.Second,
			MinimumTimeout:     3 * time.Second,
			MaximumTimeout:     5 * time.Minute,
			TimeoutCoefficient: 1,
			TimeoutHalflife:    5 * time.Minute,
		},
		benchlist.NewNoBenchlist(),
		"",
		prometheus.NewRegistry(),
	)
	if err != nil {
		t.Fatal(err)
	}
	go tm.Dispatch()

	// Create a router
	chainRouter := ChainRouter{}
	err = chainRouter.Initialize(ids.ShortEmpty, logging.NoLog{}, &tm, time.Hour, time.Millisecond, ids.Set{}, nil, HealthConfig{}, "", prometheus.NewRegistry())
	assert.NoError(t, err)

	// Create an engine and handler
	engine := common.EngineTest{T: t}
	engine.Default(false)

	engine.ContextF = snow.DefaultContextTest

	vdrs := validators.NewSet()
	err = vdrs.AddWeight(ids.GenerateTestShortID(), 1)
	assert.NoError(t, err)
	handler := &Handler{}
	err = handler.Initialize(
		&engine,
		vdrs,
		nil,
		"",
		prometheus.NewRegistry(),
	)
	assert.NoError(t, err)

	chainRouter.AddChain(handler)
	go handler.Dispatch()

	// Register requests for each request type
	msgs := []constants.MsgType{
		constants.GetMsg,
		constants.GetAncestorsMsg,
		constants.PullQueryMsg,
		constants.PushQueryMsg,
		constants.GetAcceptedMsg,
		constants.GetAcceptedFrontierMsg,
	}

	vID := ids.GenerateTestShortID()
	for i, msg := range msgs {
		chainRouter.RegisterRequest(vID, handler.ctx.ChainID, uint32(i), msg)
	}

	// Clear each timeout by simulating responses to the queries
	// Note: Depends on the ordering of [msgs]
	chainRouter.Put(vID, handler.ctx.ChainID, 0, ids.GenerateTestID(), nil, nil)
	chainRouter.MultiPut(vID, handler.ctx.ChainID, 1, nil, nil)
	chainRouter.Chits(vID, handler.ctx.ChainID, 2, nil, nil)
	chainRouter.Chits(vID, handler.ctx.ChainID, 3, nil, nil)
	chainRouter.Accepted(vID, handler.ctx.ChainID, 4, nil, nil)
	chainRouter.AcceptedFrontier(vID, handler.ctx.ChainID, 5, nil, nil)

	assert.Equal(t, chainRouter.timedRequests.Len(), 0)
}
<|endoftext|>


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

package buildkite

import (
	"fmt"
	"time"
)

// BuildsService handles communication with the build related
// methods of the buildkite API.
//
// buildkite API docs: https://buildkite.com/docs/api/builds
type BuildsService struct {
	client *Client
}

// Creator represents who created a build
type Creator struct {
	AvatarURL string     `json:"avatar_url"`
	CreatedAt *Timestamp `json:"created_at"`
	Email     string     `json:"email"`
	ID        string     `json:"id"`
	Name      string     `json:"name"`
}

// Build represents a build which has run in buildkite
type Build struct {
	ID          *string                `json:"id,omitempty"`
	URL         *string                `json:"url,omitempty"`
	WebURL      *string                `json:"web_url,omitempty"`
	Number      *int                   `json:"number,omitempty"`
	State       *string                `json:"state,omitempty"`
	Message     *string                `json:"message,omitempty"`
	Commit      *string                `json:"commit,omitempty"`
	Branch      *string                `json:"branch,omitempty"`
	Env         map[string]interface{} `json:"env,omitempty"`
	CreatedAt   *Timestamp             `json:"created_at,omitempty"`
	ScheduledAt *Timestamp             `json:"scheduled_at,omitempty"`
	StartedAt   *Timestamp             `json:"started_at,omitempty"`
	FinishedAt  *Timestamp             `json:"finished_at,omitempty"`
	MetaData    interface{}            `json:"meta_data,omitempty"`
	Creator     *Creator               `json:"creator,omitempty"`

	// jobs run during the build
	Jobs []*Job `json:"jobs,omitempty"`

	// the pipeline this build is associated with
	Pipeline *Pipeline `json:"pipeline,omitempty"`
}

// Job represents a job run during a build in buildkite
type Job struct {
	ID              *string    `json:"id,omitempty"`
	Type            *string    `json:"type,omitempty"`
	Name            *string    `json:"name,omitempty"`
	State           *string    `json:"state,omitempty"`
	LogsURL         *string    `json:"logs_url,omitempty"`
	RawLogsURL      *string    `json:"raw_log_url,omitempty"`
	Command         *string    `json:"command,omitempty"`
	ExitStatus      *int       `json:"exit_status,omitempty"`
	ArtifactPaths   *string    `json:"artifact_paths,omitempty"`
	CreatedAt       *Timestamp `json:"created_at,omitempty"`
	ScheduledAt     *Timestamp `json:"scheduled_at,omitempty"`
	StartedAt       *Timestamp `json:"started_at,omitempty"`
	FinishedAt      *Timestamp `json:"finished_at,omitempty"`
	Agent           Agent      `json:"agent,omitempty"`
	AgentQueryRules []string   `json:"agent_query_rules,omitempty"`
	WebURL          string     `json:"web_url"`
}

// BuildsListOptions specifies the optional parameters to the
// BuildsService.List method.
type BuildsListOptions struct {

	// Filters the results by the user who created the build
	Creator string `url:"creator,omitempty"`

	// Filters the results by builds created on or after the given time
	CreatedFrom time.Time `url:"created_from,omitempty"`

	// Filters the results by builds created before the given time
	CreatedTo time.Time `url:"created_to,omitempty"`

	// Filters the results by builds finished on or after the given time
	FinishedFrom time.Time `url:"finished_from,omitempty"`

	// State of builds to list.  Possible values are: running, scheduled, passed,
	// failed, canceled, skipped and not_run. Default is "".
	State []string `url:"state,brackets,omitempty"`

	// Branch filter by the name of the branch. Default is "".
	Branch string `url:"branch,omitempty"`

	ListOptions
}

// Get fetches a build.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#get-a-build
func (as *BuildsService) Get(org string, pipeline string, id string) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds/%s", org, pipeline, id)

	req, err := as.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// List the builds for the current user.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-all-builds
func (bs *BuildsService) List(opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/builds")

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByOrg lists the builds within the specified orginisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-an-organization
func (bs *BuildsService) ListByOrg(org string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/builds", org)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByPipeline lists the builds for a pipeline within the specified originisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-a-pipeline
func (bs *BuildsService) ListByPipeline(org string, pipeline string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}
<commit_msg>Add Create() to BuildsService<commit_after>// Copyright 2014 Mark Wolfe. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package buildkite

import (
	"fmt"
	"time"
)

// BuildsService handles communication with the build related
// methods of the buildkite API.
//
// buildkite API docs: https://buildkite.com/docs/api/builds
type BuildsService struct {
	client *Client
}

// Author of a commit (used in CreateBuild)
type Author struct {
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

// Create a build.
type CreateBuild struct {
	Commit  string `json:"commit"`
	Branch  string `json:"branch"`
	Message string `json:"message"`

	// Optional fields
	Author                      Author            `json:"author,omitempty"`
	Env                         map[string]string `json:"env,omitempty"`
	MetaData                    map[string]string `json:"meta_data,omitempty"`
	IgnorePipelineBranchFilters bool              `json:"ignore_pipeline_branch_filters,omitempty"`
}

// Creator represents who created a build
type Creator struct {
	AvatarURL string     `json:"avatar_url"`
	CreatedAt *Timestamp `json:"created_at"`
	Email     string     `json:"email"`
	ID        string     `json:"id"`
	Name      string     `json:"name"`
}

// Build represents a build which has run in buildkite
type Build struct {
	ID          *string                `json:"id,omitempty"`
	URL         *string                `json:"url,omitempty"`
	WebURL      *string                `json:"web_url,omitempty"`
	Number      *int                   `json:"number,omitempty"`
	State       *string                `json:"state,omitempty"`
	Message     *string                `json:"message,omitempty"`
	Commit      *string                `json:"commit,omitempty"`
	Branch      *string                `json:"branch,omitempty"`
	Env         map[string]interface{} `json:"env,omitempty"`
	CreatedAt   *Timestamp             `json:"created_at,omitempty"`
	ScheduledAt *Timestamp             `json:"scheduled_at,omitempty"`
	StartedAt   *Timestamp             `json:"started_at,omitempty"`
	FinishedAt  *Timestamp             `json:"finished_at,omitempty"`
	MetaData    interface{}            `json:"meta_data,omitempty"`
	Creator     *Creator               `json:"creator,omitempty"`

	// jobs run during the build
	Jobs []*Job `json:"jobs,omitempty"`

	// the pipeline this build is associated with
	Pipeline *Pipeline `json:"pipeline,omitempty"`
}

// Job represents a job run during a build in buildkite
type Job struct {
	ID              *string    `json:"id,omitempty"`
	Type            *string    `json:"type,omitempty"`
	Name            *string    `json:"name,omitempty"`
	State           *string    `json:"state,omitempty"`
	LogsURL         *string    `json:"logs_url,omitempty"`
	RawLogsURL      *string    `json:"raw_log_url,omitempty"`
	Command         *string    `json:"command,omitempty"`
	ExitStatus      *int       `json:"exit_status,omitempty"`
	ArtifactPaths   *string    `json:"artifact_paths,omitempty"`
	CreatedAt       *Timestamp `json:"created_at,omitempty"`
	ScheduledAt     *Timestamp `json:"scheduled_at,omitempty"`
	StartedAt       *Timestamp `json:"started_at,omitempty"`
	FinishedAt      *Timestamp `json:"finished_at,omitempty"`
	Agent           Agent      `json:"agent,omitempty"`
	AgentQueryRules []string   `json:"agent_query_rules,omitempty"`
	WebURL          string     `json:"web_url"`
}

// BuildsListOptions specifies the optional parameters to the
// BuildsService.List method.
type BuildsListOptions struct {

	// Filters the results by the user who created the build
	Creator string `url:"creator,omitempty"`

	// Filters the results by builds created on or after the given time
	CreatedFrom time.Time `url:"created_from,omitempty"`

	// Filters the results by builds created before the given time
	CreatedTo time.Time `url:"created_to,omitempty"`

	// Filters the results by builds finished on or after the given time
	FinishedFrom time.Time `url:"finished_from,omitempty"`

	// State of builds to list.  Possible values are: running, scheduled, passed,
	// failed, canceled, skipped and not_run. Default is "".
	State []string `url:"state,brackets,omitempty"`

	// Branch filter by the name of the branch. Default is "".
	Branch string `url:"branch,omitempty"`

	ListOptions
}

func (as *BuildsService) Create(org string, pipeline string, b *CreateBuild) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	req, err := as.client.NewRequest("POST", u, b)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// Get fetches a build.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#get-a-build
func (as *BuildsService) Get(org string, pipeline string, id string) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds/%s", org, pipeline, id)

	req, err := as.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// List the builds for the current user.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-all-builds
func (bs *BuildsService) List(opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/builds")

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByOrg lists the builds within the specified orginisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-an-organization
func (bs *BuildsService) ListByOrg(org string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/builds", org)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByPipeline lists the builds for a pipeline within the specified originisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-a-pipeline
func (bs *BuildsService) ListByPipeline(org string, pipeline string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}
<|endoftext|>


<commit_before>package main

import "fmt"

const BuilderId = "lmars.post-processor.vagrant-s3"

type Artifact struct {
	Url string
}

func (*Artifact) BuilderId() string {
	return BuilderId
}

func (a *Artifact) Files() []string {
	return nil
}

func (a *Artifact) Id() string {
	return ""
}

func (a *Artifact) String() string {
	return fmt.Sprintf("Vagrant manifest url: %s", a.Url)
}

func (a *Artifact) Destroy() error {
	return nil
}
<commit_msg>Impleented State(name string) method of the packer.Artifact interface<commit_after>package main

import "fmt"

const BuilderId = "lmars.post-processor.vagrant-s3"

type Artifact struct {
	Url string
}

func (*Artifact) BuilderId() string {
	return BuilderId
}

func (a *Artifact) Files() []string {
	return nil
}

func (a *Artifact) Id() string {
	return ""
}

func (a *Artifact) String() string {
	return fmt.Sprintf("Vagrant manifest url: %s", a.Url)
}

func (a *Artifact) State(name string) interface{} {
	return nil
}

func (a *Artifact) Destroy() error {
	return nil
}
<|endoftext|>


<commit_before>package bytes

import "testing"

func TestFormat(t *testing.T) {
	// B
	f := Format(515)
	if f != "515 B" {
		t.Errorf("formatted bytes should be 515 B, found %s", f)
	}

	// MB
	f = Format(13231323)
	if f != "13.23 MB" {
		t.Errorf("formatted bytes should be 13.23 MB, found %s", f)
	}

	// Exact
	f = Format(1000 * 1000 * 1000)
	if f != "1.00 GB" {
		t.Errorf("formatted bytes should be 1.00 GB, found %s xxx", f)
	}
}

func TestFormatB(t *testing.T) {
	f := FormatB(1323)
	if f != "1.29 KiB" {
		t.Errorf("formatted bytes should be 1.29 KiB, found %s xxx", f)
	}
}
<commit_msg>Fixed a typo<commit_after>package bytes

import "testing"

func TestFormat(t *testing.T) {
	// B
	f := Format(515)
	if f != "515 B" {
		t.Errorf("formatted bytes should be 515 B, found %s", f)
	}

	// MB
	f = Format(13231323)
	if f != "13.23 MB" {
		t.Errorf("formatted bytes should be 13.23 MB, found %s", f)
	}

	// Exact
	f = Format(1000 * 1000 * 1000)
	if f != "1.00 GB" {
		t.Errorf("formatted bytes should be 1.00 GB, found %s", f)
	}
}

func TestFormatB(t *testing.T) {
	f := FormatB(1323)
	if f != "1.29 KiB" {
		t.Errorf("formatted bytes should be 1.29 KiB, found %s", f)
	}
}
<|endoftext|>


<commit_before>package cache

import (
	"fmt"
	"strconv"
	"testing"
	"time"
)

var (
	ValidHost = "localhost:6379"

	TestKey   = "Test:" + strconv.Itoa(int(time.Now().Unix()))
	TestValue = "Value:" + strconv.Itoa(int(time.Now().Unix()))
)

func TestNew(t *testing.T) {
	cache := New(ValidHost)
	if cache == nil {
		t.Error("Nil cache returned!")
	}
}

func TestCache_PutString(t *testing.T) {
	cache := New(ValidHost)

	if ok, err := cache.PutString(TestKey, TestValue); err != nil {
		t.Error(err)
	} else if ok != "OK" {
		t.Error("Unexpected cache response:", ok)
	}
}

func TestCache_GetString(t *testing.T) {
	cache := New(ValidHost)

	if res, err := cache.GetString(TestKey); err != nil {
		t.Error(err)
	} else if res != TestValue {
		t.Error("Unexpected result:", res)
	}
}

func TestCache_Delete(t *testing.T) {
	cache := New(ValidHost)

	// Delete should not return an error
	if err := cache.Delete(TestKey); err != nil {
		t.Error(err)
	}

	// Calling again, on a deleted key, should still not fail
	if err := cache.Delete(TestKey); err != nil {
		t.Error(err)
	}
}

func TestCache_GetString_DeletedKey(t *testing.T) {
	cache := New(ValidHost)

	if _, err := cache.GetString(TestKey); err == nil {
		t.Error("Expected error getting deleted key!")
	}
}

func TestCache_Lock(t *testing.T) {
	cache := New(ValidHost)

	key := fmt.Sprintf("testLock:%v", time.Now().Unix())
	value := "avalue"

	// Base test
	if locked, err := cache.Lock(key, value, 1000); err != nil {
		t.Fatal(err)
	} else if !locked {
		t.Fatal("Expected valid lock to return true")
	}

	// Try to lock the same key
	if locked, err := cache.Lock(key, value, 1000); err != nil {
		t.Fatal(err)
	} else if locked {
		t.Fatal("Expected invalid lock to return false")
	}

	// Wait
	time.Sleep(time.Millisecond * 1000)

	// Try again
	if locked, err := cache.Lock(key, value, 1000); err != nil {
		t.Fatal(err)
	} else if !locked {
		t.Fatal("Expected valid lock to return true")
	}
}

func TestCache_Unlock(t *testing.T) {
	cache := New(ValidHost)

	key := fmt.Sprintf("testUnlock:%v", time.Now().Unix())
	value := "avalue"

	cache.Lock(key, value, 1000)

	// Bad key
	if err := cache.Unlock("badkey", value); err != ErrCantUnlock {
		t.Fatal(err)
	}

	// Bad value
	if err := cache.Unlock(key, "badvalue"); err != ErrCantUnlock {
		t.Fatal(err)
	}

	// Valid
	if err := cache.Unlock(key, value); err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Fix race condition in cache lock test<commit_after>package cache

import (
	"fmt"
	"strconv"
	"testing"
	"time"
)

var (
	ValidHost = "localhost:6379"

	TestKey   = "Test:" + strconv.Itoa(int(time.Now().Unix()))
	TestValue = "Value:" + strconv.Itoa(int(time.Now().Unix()))
)

func TestNew(t *testing.T) {
	cache := New(ValidHost)
	if cache == nil {
		t.Error("Nil cache returned!")
	}
}

func TestCache_PutString(t *testing.T) {
	cache := New(ValidHost)

	if ok, err := cache.PutString(TestKey, TestValue); err != nil {
		t.Error(err)
	} else if ok != "OK" {
		t.Error("Unexpected cache response:", ok)
	}
}

func TestCache_GetString(t *testing.T) {
	cache := New(ValidHost)

	if res, err := cache.GetString(TestKey); err != nil {
		t.Error(err)
	} else if res != TestValue {
		t.Error("Unexpected result:", res)
	}
}

func TestCache_Delete(t *testing.T) {
	cache := New(ValidHost)

	// Delete should not return an error
	if err := cache.Delete(TestKey); err != nil {
		t.Error(err)
	}

	// Calling again, on a deleted key, should still not fail
	if err := cache.Delete(TestKey); err != nil {
		t.Error(err)
	}
}

func TestCache_GetString_DeletedKey(t *testing.T) {
	cache := New(ValidHost)

	if _, err := cache.GetString(TestKey); err == nil {
		t.Error("Expected error getting deleted key!")
	}
}

func TestCache_Lock(t *testing.T) {
	cache := New(ValidHost)

	key := fmt.Sprintf("testLock:%v", time.Now().Unix())
	value := "avalue"

	// Base test
	if locked, err := cache.Lock(key, value, 1000); err != nil {
		t.Fatal(err)
	} else if !locked {
		t.Fatal("Expected valid lock to return true")
	}

	// Try to lock the same key
	if locked, err := cache.Lock(key, value, 1000); err != nil {
		t.Fatal(err)
	} else if locked {
		t.Fatal("Expected invalid lock to return false")
	}

	// Wait
	time.Sleep(time.Millisecond * 1100)

	// Try again
	if locked, err := cache.Lock(key, value, 1000); err != nil {
		t.Fatal(err)
	} else if !locked {
		t.Fatal("Expected valid lock to return true")
	}
}

func TestCache_Unlock(t *testing.T) {
	cache := New(ValidHost)

	key := fmt.Sprintf("testUnlock:%v", time.Now().Unix())
	value := "avalue"

	cache.Lock(key, value, 1000)

	// Bad key
	if err := cache.Unlock("badkey", value); err != ErrCantUnlock {
		t.Fatal(err)
	}

	// Bad value
	if err := cache.Unlock(key, "badvalue"); err != ErrCantUnlock {
		t.Fatal(err)
	}

	// Valid
	if err := cache.Unlock(key, value); err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Package apps provides access to metadata about applications distributed on
// Steam platform.
package apps

import (
	"database/sql"
	"os"
	"time"

	"bitbucket.org/kardianos/osext"
	_ "github.com/mattn/go-sqlite3"
	"github.com/steamhistory/steamhistory/steam"
)

const (
	MetadataDBLocation = "data"
	MetadataDBName     = "metadata.db" // Name of the database with metadata about apps
)

// OpenMetadataDB opens database with metadata about all apps and, if successful,
// returns a reference to it.
func OpenMetadataDB() (*sql.DB, error) {
	exeloc, err := osext.ExecutableFolder()
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(exeloc+MetadataDBLocation, 0774)
	if err != nil {
		return nil, err
	}
	db, err := sql.Open("sqlite3", exeloc+MetadataDBLocation+MetadataDBName)
	if err != nil {
		return nil, err
	}
	// TODO: Check if file exists before attempting to create a table
	_, err = db.Exec(`
		CREATE TABLE IF NOT EXISTS metadata (
			id INTEGER NOT NULL PRIMARY KEY,
			name TEXT,
			usable BOOLEAN NOT NULL DEFAULT 1,
			lastUpdate DATETIME NOT NULL
		);
		`)
	if err != nil {
		return nil, err
	}
	return db, nil
}

func UpdateMetadata() error {
	apps, err := steam.GetApps()
	if err != nil {
		return err
	}
	return SaveMetadata(apps)
}

func SaveMetadata(apps []steam.App) error {
	db, err := OpenMetadataDB()
	if err != nil {
		return err
	}
	defer db.Close()
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	stmt, err := tx.Prepare(`
		INSERT OR REPLACE INTO metadata (id, usable, name, lastUpdate)
  		VALUES (?, COALESCE((SELECT usable FROM metadata WHERE id=?), 1), ?, ?);
		`)
	if err != nil {
		return err
	}
	defer stmt.Close()
	for i := range apps {
		_, err = stmt.Exec(apps[i].ID, apps[i].ID, apps[i].Name, time.Now().UTC().Unix())
		if err != nil {
			return err
		}
	}
	tx.Commit()
	return nil
}

// MarkAppAsUnusable marks application as unusable.
func MarkAppAsUnusable(appId int) error {
	db, err := OpenMetadataDB()
	if err != nil {
		return err
	}
	defer db.Close()
	_, err = db.Exec("UPDATE metadata SET usable=0 WHERE id=?", appId)
	if err != nil {
		return err
	}
	return nil
}

// MarkAppAsUsable marks application as usable.
func MarkAppAsUsable(appId int) error {
	db, err := OpenMetadataDB()
	if err != nil {
		return err
	}
	defer db.Close()
	_, err = db.Exec("UPDATE metadata SET usable=1 WHERE id=?", appId)
	if err != nil {
		return err
	}
	return nil
}

// AllUsableApps returns a slice with all usable applications.
func AllUsableApps() ([]steam.App, error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return nil, err
	}
	defer db.Close()

	rows, err := db.Query("SELECT id, name FROM metadata WHERE usable=1")
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var apps []steam.App
	for rows.Next() {
		var app steam.App
		err := rows.Scan(&app.ID, &app.Name)
		if err != nil {
			return nil, err
		}
		apps = append(apps, app)
	}
	return apps, nil
}

// AllUnusableApps returns a slice with all unusable applications.
func AllUnusableApps() ([]steam.App, error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return nil, err
	}
	defer db.Close()

	rows, err := db.Query("SELECT id, name FROM metadata WHERE usable=0")
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var apps []steam.App
	for rows.Next() {
		var app steam.App
		err := rows.Scan(&app.ID, &app.Name)
		if err != nil {
			return nil, err
		}
		apps = append(apps, app)
	}
	return apps, nil
}

// GetName returns name of the specified application.
func GetName(appId int) (name string, err error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return name, err
	}
	defer db.Close()

	stmt, err := db.Prepare("SELECT name FROM metadata WHERE id=?")
	if err != nil {
		return name, err
	}
	defer stmt.Close()

	err = stmt.QueryRow(appId).Scan(&name)
	if err != nil {
		return name, err
	}
	return name, nil
}

// Search function finds applications that have name simmilar to one that is
// specified in a query. It's case insensitive. Returns at most 10 results.
func Search(query string) (apps []steam.App, err error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return nil, err
	}
	defer db.Close()

	stmt, err := db.Prepare(`
		SELECT id, name
		FROM metadata
		WHERE usable=1 AND name LIKE ?
			AND UPPER(name) LIKE ?
		LIMIT 10
		`)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	query = "%" + query + "%"
	rows, err := stmt.Query(query, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var app steam.App
		err := rows.Scan(&app.ID, &app.Name)
		if err != nil {
			return nil, err
		}
		apps = append(apps, app)
	}
	rows.Close()
	return apps, nil
}
<commit_msg>Fixed creation of the path to metadata DB.<commit_after>// Package apps provides access to metadata about applications distributed on
// Steam platform.
package apps

import (
	"database/sql"
	"os"
	"time"

	"bitbucket.org/kardianos/osext"
	_ "github.com/mattn/go-sqlite3"
	"github.com/steamhistory/steamhistory/steam"
)

const (
	MetadataDBLocation = "data"
	MetadataDBName     = "metadata.db" // Name of the database with metadata about apps
)

// OpenMetadataDB opens database with metadata about all apps and, if successful,
// returns a reference to it.
func OpenMetadataDB() (*sql.DB, error) {
	exeloc, err := osext.ExecutableFolder()
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(exeloc+MetadataDBLocation, 0774)
	if err != nil {
		return nil, err
	}
	db, err := sql.Open("sqlite3", exeloc+MetadataDBLocation+"/"+MetadataDBName)
	if err != nil {
		return nil, err
	}
	// TODO: Check if file exists before attempting to create a table
	_, err = db.Exec(`
		CREATE TABLE IF NOT EXISTS metadata (
			id INTEGER NOT NULL PRIMARY KEY,
			name TEXT,
			usable BOOLEAN NOT NULL DEFAULT 1,
			lastUpdate DATETIME NOT NULL
		);
		`)
	if err != nil {
		return nil, err
	}
	return db, nil
}

func UpdateMetadata() error {
	apps, err := steam.GetApps()
	if err != nil {
		return err
	}
	return SaveMetadata(apps)
}

func SaveMetadata(apps []steam.App) error {
	db, err := OpenMetadataDB()
	if err != nil {
		return err
	}
	defer db.Close()
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	stmt, err := tx.Prepare(`
		INSERT OR REPLACE INTO metadata (id, usable, name, lastUpdate)
  		VALUES (?, COALESCE((SELECT usable FROM metadata WHERE id=?), 1), ?, ?);
		`)
	if err != nil {
		return err
	}
	defer stmt.Close()
	for i := range apps {
		_, err = stmt.Exec(apps[i].ID, apps[i].ID, apps[i].Name, time.Now().UTC().Unix())
		if err != nil {
			return err
		}
	}
	tx.Commit()
	return nil
}

// MarkAppAsUnusable marks application as unusable.
func MarkAppAsUnusable(appId int) error {
	db, err := OpenMetadataDB()
	if err != nil {
		return err
	}
	defer db.Close()
	_, err = db.Exec("UPDATE metadata SET usable=0 WHERE id=?", appId)
	if err != nil {
		return err
	}
	return nil
}

// MarkAppAsUsable marks application as usable.
func MarkAppAsUsable(appId int) error {
	db, err := OpenMetadataDB()
	if err != nil {
		return err
	}
	defer db.Close()
	_, err = db.Exec("UPDATE metadata SET usable=1 WHERE id=?", appId)
	if err != nil {
		return err
	}
	return nil
}

// AllUsableApps returns a slice with all usable applications.
func AllUsableApps() ([]steam.App, error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return nil, err
	}
	defer db.Close()

	rows, err := db.Query("SELECT id, name FROM metadata WHERE usable=1")
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var apps []steam.App
	for rows.Next() {
		var app steam.App
		err := rows.Scan(&app.ID, &app.Name)
		if err != nil {
			return nil, err
		}
		apps = append(apps, app)
	}
	return apps, nil
}

// AllUnusableApps returns a slice with all unusable applications.
func AllUnusableApps() ([]steam.App, error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return nil, err
	}
	defer db.Close()

	rows, err := db.Query("SELECT id, name FROM metadata WHERE usable=0")
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var apps []steam.App
	for rows.Next() {
		var app steam.App
		err := rows.Scan(&app.ID, &app.Name)
		if err != nil {
			return nil, err
		}
		apps = append(apps, app)
	}
	return apps, nil
}

// GetName returns name of the specified application.
func GetName(appId int) (name string, err error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return name, err
	}
	defer db.Close()

	stmt, err := db.Prepare("SELECT name FROM metadata WHERE id=?")
	if err != nil {
		return name, err
	}
	defer stmt.Close()

	err = stmt.QueryRow(appId).Scan(&name)
	if err != nil {
		return name, err
	}
	return name, nil
}

// Search function finds applications that have name simmilar to one that is
// specified in a query. It's case insensitive. Returns at most 10 results.
func Search(query string) (apps []steam.App, err error) {
	db, err := OpenMetadataDB()
	if err != nil {
		return nil, err
	}
	defer db.Close()

	stmt, err := db.Prepare(`
		SELECT id, name
		FROM metadata
		WHERE usable=1 AND name LIKE ?
			AND UPPER(name) LIKE ?
		LIMIT 10
		`)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	query = "%" + query + "%"
	rows, err := stmt.Query(query, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var app steam.App
		err := rows.Scan(&app.ID, &app.Name)
		if err != nil {
			return nil, err
		}
		apps = append(apps, app)
	}
	rows.Close()
	return apps, nil
}
<|endoftext|>


<commit_before>package time_test

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/cheekybits/is"

	c4 "github.com/Avalanche-io/c4/time"
)

const testtime = "Fri Aug 29 2:14:00 EDT 1997"

func TestNewTime(t *testing.T) {
	// init
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	// ut 'universal time'
	ut := c4.NewTime(testday)
	is.Equal("1997-08-29T06:14:00Z", string(ut))
}

func equalTime(t1 time.Time, t2 time.Time) bool {
	return t1.Sub(t2) < time.Millisecond
}

func TestNow(t *testing.T) {
	// init
	is := is.New(t)
	ut := c4.Now()
	is.True(equalTime(ut.AsTime(), time.Now()))
}

func TestTimeNil(t *testing.T) {
	// init
	is := is.New(t)
	var ut c4.Time
	is.True(ut.Nil())
}

func TestTimeAsTime(t *testing.T) {
	// init
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	ut := c4.NewTime(testday)
	is.Equal(ut.AsTime(), testday.In(time.Local))
}

func TestTimeAge(t *testing.T) {
	// init
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	ut := c4.NewTime(testday)
	dif := time.Now().Sub(testday)
	// There will be slight difference in ut.Age call to time.Now, and
	// the one in the test, perhaps 10 microseconds is reasonable margin.
	// Or else use the equalTime function for much larger margin.
	is.Equal(ut.Age()/(100*time.Microsecond), dif/(100*time.Microsecond))
}

func TestTimeJSON(t *testing.T) {
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	ut := c4.NewTime(testday)
	is.Equal("1997-08-29T06:14:00Z", string(ut))
	data, err := json.Marshal(ut)
	is.NoErr(err)
	is.Equal("\"1997-08-29T06:14:00Z\"", string(data))
}
<commit_msg>increasted tolerance in time test again<commit_after>package time_test

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/cheekybits/is"

	c4 "github.com/Avalanche-io/c4/time"
)

const testtime = "Fri Aug 29 2:14:00 EDT 1997"

func TestNewTime(t *testing.T) {
	// init
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	// ut 'universal time'
	ut := c4.NewTime(testday)
	is.Equal("1997-08-29T06:14:00Z", string(ut))
}

func equalTime(t1 time.Time, t2 time.Time) bool {
	return t1.Sub(t2) < time.Millisecond
}

func TestNow(t *testing.T) {
	// init
	is := is.New(t)
	ut := c4.Now()
	is.True(equalTime(ut.AsTime(), time.Now()))
}

func TestTimeNil(t *testing.T) {
	// init
	is := is.New(t)
	var ut c4.Time
	is.True(ut.Nil())
}

func TestTimeAsTime(t *testing.T) {
	// init
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	ut := c4.NewTime(testday)
	is.Equal(ut.AsTime(), testday.In(time.Local))
}

func TestTimeAge(t *testing.T) {
	// init
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	ut := c4.NewTime(testday)
	dif := time.Now().Sub(testday)
	// There will be slight difference in ut.Age call to time.Now, and
	// the one in the test, perhaps 10 microseconds is reasonable margin.
	// Or else use the equalTime function for much larger margin.
	is.Equal(ut.Age()/(time.Millisecond), dif/(time.Millisecond))
}

func TestTimeJSON(t *testing.T) {
	is := is.New(t)
	loc, err := time.LoadLocation("US/Eastern")
	is.NoErr(err)
	testday, err := time.ParseInLocation(time.UnixDate, testtime, loc)
	is.NoErr(err)
	ut := c4.NewTime(testday)
	is.Equal("1997-08-29T06:14:00Z", string(ut))
	data, err := json.Marshal(ut)
	is.NoErr(err)
	is.Equal("\"1997-08-29T06:14:00Z\"", string(data))
}
<|endoftext|>


<commit_before>package kontrol

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"testing"
	"time"

	"github.com/koding/kite"
	"github.com/koding/kite/config"
	"github.com/koding/kite/protocol"
	"github.com/koding/kite/proxy"
	"github.com/koding/kite/registration"
	"github.com/koding/kite/testkeys"
	"github.com/koding/kite/testutil"
)

var (
	conf *config.Config
	kon  *Kontrol
)

func init() {
	conf = config.New()
	conf.Username = "testuser"
	conf.KontrolURL = &url.URL{Scheme: "ws", Host: "localhost:4000"}
	conf.KontrolKey = testkeys.Public
	conf.KontrolUser = "testuser"
	conf.KiteKey = testutil.NewKiteKey().Raw

	kon = New(conf.Copy(), "0.0.1", testkeys.Public, testkeys.Private)
	kon.DataDir, _ = ioutil.TempDir("", "")
	defer os.RemoveAll(kon.DataDir)
	kon.Start()
}

func TestGetKites(t *testing.T) {
	t.Log("Setting up mathworker4")

	testName := "mathwork4"
	testVersion := "1.1.1"
	m := kite.New(testName, testVersion)
	m.Config = conf.Copy()

	t.Log("Registering ", testName)
	kiteURL := &url.URL{Scheme: "ws", Host: "localhost:4444"}
	_, err := m.Register(kiteURL)
	if err != nil {
		t.Error(err)
	}
	defer m.Close()

	query := protocol.KontrolQuery{
		Username:    conf.Username,
		Environment: conf.Environment,
		Name:        testName,
		Version:     "~> 1.1",
	}

	// exp2 queries for mathkite
	t.Log("Querying for mathworker4")
	exp3 := kite.New("exp3", "0.0.1")
	exp3.Config = conf.Copy()
	kites, err := exp3.GetKites(query)
	if err != nil {
		t.Fatal(err)
	}

	if len(kites) == 0 {
		t.Fatal("No mathworker available")
	}

	if len(kites) != 1 {
		t.Fatal("Only one kite is registerd, we have %d", len(kites))
	}

	if kites[0].Name != testName {
		t.Error("getkites got %s exptected %", kites[0].Name, testName)
	}

	if kites[0].Version != testVersion {
		t.Error("getkites got %s exptected %", kites[0].Version, testVersion)
	}
}

func TestRegister(t *testing.T) {
	t.Log("Setting up mathworker3")
	kiteURL := &url.URL{Scheme: "ws", Host: "localhost:4444"}
	m := kite.New("mathworker3", "1.1.1")
	m.Config = conf.Copy()

	t.Log("Registering mathworker")
	res, err := m.Register(kiteURL)
	if err != nil {
		t.Error(err)
	}
	defer m.Close()

	if kiteURL.String() != res.URL.String() {
		t.Error("register: got %s expected %s", res.URL.String(), kiteURL.String())
	}
}

func TestKontrol(t *testing.T) {
	t.Log("Setting up proxy")
	prx := proxy.New(conf.Copy(), "0.0.1", testkeys.Public, testkeys.Private)
	prx.Start()

	time.Sleep(1e9)

	// Start mathworker
	t.Log("Setting up mathworker")
	mathKite := kite.New("mathworker", "1.2.3")
	mathKite.Config = conf.Copy()
	mathKite.HandleFunc("square", Square)
	mathKite.Start()

	reg := registration.New(mathKite)
	go reg.RegisterToProxyAndKontrol()
	<-reg.ReadyNotify()

	// exp2 kite is the mathworker client
	t.Log("Setting up exp2 kite")
	exp2Kite := kite.New("exp2", "0.0.1")
	exp2Kite.Config = conf.Copy()

	query := protocol.KontrolQuery{
		Username:    exp2Kite.Kite().Username,
		Environment: exp2Kite.Kite().Environment,
		Name:        "mathworker",
		Version:     "~> 1.1",
	}

	// exp2 queries for mathkite
	t.Log("Querying for mathworkers")
	kites, err := exp2Kite.GetKites(query)
	if err != nil {
		t.Fatal(err)
	}

	if len(kites) == 0 {
		t.Fatal("No mathworker available")
	}

	// exp2 connectes to mathworker
	remoteMathWorker := kites[0]
	err = remoteMathWorker.Dial()
	if err != nil {
		t.Fatal("Cannot connect to remote mathworker", err)
	}

	// Test Kontrol.GetToken
	t.Logf("oldToken: %s", remoteMathWorker.Authentication.Key)
	newToken, err := exp2Kite.GetToken(&remoteMathWorker.Kite)
	if err != nil {
		t.Error(err)
	}
	t.Logf("newToken: %s", newToken)

	// Run "square" method
	response, err := remoteMathWorker.Tell("square", 2)
	if err != nil {
		t.Fatal(err)
	}

	var result int
	err = response.Unmarshal(&result)
	if err != nil {
		t.Fatal(err)
	}

	// Result must be "4"
	if result != 4 {
		t.Fatalf("Invalid result: %d", result)
	}

	events := make(chan *kite.Event, 3)

	// Test WatchKites
	t.Log("calling  watchkites")
	watcher, err := exp2Kite.WatchKites(query, func(e *kite.Event, err *kite.Error) {
		if err != nil {
			t.Fatal(err)
		}

		t.Logf("Event.Action: %s Event.Kite.ID: %s", e.Action, e.Kite.ID)
		events <- e
	})
	if err != nil {
		t.Fatalf("Cannot watch: %s", err.Error())
	}

	// First event must be register event because math worker is already running
	select {
	case e := <-events:
		if e.Action != protocol.Register {
			t.Fatalf("unexpected action: %s", e.Action)
		}
	case <-time.After(time.Second):
		t.Fatal("timeout")
	}

	t.Log("closing mathworker")
	mathKite.Close()

	// We must get Deregister event
	select {
	case e := <-events:
		if e.Action != protocol.Deregister {
			t.Fatalf("unexpected action: %s", e.Action)
		}
	case <-time.After(time.Second):
		t.Fatal("timeout")
	}

	// Start a new mathworker kite
	t.Log("Setting up mathworker2")
	mathKite2 := kite.New("mathworker", "1.2.3")
	mathKite2.Config = conf.Copy()
	mathKite2.Start()

	reg2 := registration.New(mathKite2)
	go reg2.RegisterToProxyAndKontrol()
	<-reg2.ReadyNotify()

	// We must get Register event
	select {
	case e := <-events:
		if e.Action != protocol.Register {
			t.Fatalf("unexpected action: %s", e.Action)
		}
	case <-time.After(time.Second):
		t.Fatal("timeout")
	}

	err = watcher.Cancel()
	if err != nil {
		t.Fatal(err)
	}

	// We must not get any event after cancelling the watcher
	select {
	case e := <-events:
		t.Fatalf("unexpected event: %s", e)
	case <-time.After(time.Second):
	}
}

func Square(r *kite.Request) (interface{}, error) {
	a, err := r.Args.One().Float64()
	if err != nil {
		return nil, err
	}

	result := a * a

	fmt.Printf("Kite call, sending result '%f' back\n", result)

	return result, nil
}

func TestGetQueryKey(t *testing.T) {
	// This query is valid because there are no gaps between query fields.
	q := &protocol.KontrolQuery{
		Username:    "cenk",
		Environment: "production",
	}
	key, err := getQueryKey(q)
	if err != nil {
		t.Errorf(err.Error())
	}
	if key != "/cenk/production" {
		t.Errorf("Unexpected key: %s", key)
	}

	// This is wrong because Environment field is empty.
	// We can't make a query on etcd because wildcards are not allowed in paths.
	q = &protocol.KontrolQuery{
		Username: "cenk",
		Name:     "fs",
	}
	key, err = getQueryKey(q)
	if err == nil {
		t.Errorf("Error is expected")
	}
	if key != "" {
		t.Errorf("Key is not expected: %s", key)
	}

	// This is also wrong becaus each query must have a non-empty username field.
	q = &protocol.KontrolQuery{
		Environment: "production",
		Name:        "fs",
	}
	key, err = getQueryKey(q)
	if err == nil {
		t.Errorf("Error is expected")
	}
	if key != "" {
		t.Errorf("Key is not expected: %s", key)
	}
}
<commit_msg>kontrol_test: add testing multiple kites<commit_after>package kontrol

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"strconv"
	"sync"
	"testing"
	"time"

	"github.com/koding/kite"
	"github.com/koding/kite/config"
	"github.com/koding/kite/protocol"
	"github.com/koding/kite/proxy"
	"github.com/koding/kite/registration"
	"github.com/koding/kite/testkeys"
	"github.com/koding/kite/testutil"
)

var (
	conf *config.Config
	kon  *Kontrol
)

func init() {
	conf = config.New()
	conf.Username = "testuser"
	conf.KontrolURL = &url.URL{Scheme: "ws", Host: "localhost:4000"}
	conf.KontrolKey = testkeys.Public
	conf.KontrolUser = "testuser"
	conf.KiteKey = testutil.NewKiteKey().Raw

	kon = New(conf.Copy(), "0.0.1", testkeys.Public, testkeys.Private)
	kon.DataDir, _ = ioutil.TempDir("", "")
	defer os.RemoveAll(kon.DataDir)
	kon.Start()
}

func TestMultiple(t *testing.T) {
	prepareKite := func(name, version string) func() {
		m := kite.New(name, version)
		m.Config = conf.Copy()

		kiteURL := &url.URL{Scheme: "ws", Host: "localhost:4444"}
		_, err := m.Register(kiteURL)
		if err != nil {
			t.Error(err)
		}

		return func() { m.Close() }
	}

	t.Log("Creating 100 example kites")
	for i := 0; i < 100; i++ {
		cls := prepareKite("example", "0.1."+strconv.Itoa(i))
		defer cls() // close them later
	}

	query := protocol.KontrolQuery{
		Username:    conf.Username,
		Environment: conf.Environment,
		Name:        "example",
	}

	t.Log("Querying for example kites")

	var wg sync.WaitGroup

	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()

			exp := kite.New("exp"+strconv.Itoa(i), "0.0.1")
			exp.Config = conf.Copy()
			kites, err := exp.GetKites(query)
			if err != nil {
				t.Fatal(err)
			}

			if len(kites) == 0 {
				t.Fatal("no example kites available")
			}

			if len(kites) != 100 {
				t.Fatal("expecting 100 kites, got %d", len(kites))
			}
		}(i)
	}

	t.Log("waiting until all getKites calls are finished")
	wg.Wait()
}

func TestGetKites(t *testing.T) {
	t.Log("Setting up mathworker4")

	testName := "mathwork4"
	testVersion := "1.1.1"
	m := kite.New(testName, testVersion)
	m.Config = conf.Copy()

	t.Log("Registering ", testName)
	kiteURL := &url.URL{Scheme: "ws", Host: "localhost:4444"}
	_, err := m.Register(kiteURL)
	if err != nil {
		t.Error(err)
	}
	defer m.Close()

	query := protocol.KontrolQuery{
		Username:    conf.Username,
		Environment: conf.Environment,
		Name:        testName,
		Version:     "~> 1.1",
	}

	// exp2 queries for mathkite
	t.Log("Querying for mathworker4")
	exp3 := kite.New("exp3", "0.0.1")
	exp3.Config = conf.Copy()
	kites, err := exp3.GetKites(query)
	if err != nil {
		t.Fatal(err)
	}

	if len(kites) == 0 {
		t.Fatal("No mathworker available")
	}

	if len(kites) != 1 {
		t.Fatal("Only one kite is registerd, we have %d", len(kites))
	}

	if kites[0].Name != testName {
		t.Error("getkites got %s exptected %", kites[0].Name, testName)
	}

	if kites[0].Version != testVersion {
		t.Error("getkites got %s exptected %", kites[0].Version, testVersion)
	}
}

func TestRegister(t *testing.T) {
	t.Log("Setting up mathworker3")
	kiteURL := &url.URL{Scheme: "ws", Host: "localhost:4444"}
	m := kite.New("mathworker3", "1.1.1")
	m.Config = conf.Copy()

	t.Log("Registering mathworker")
	res, err := m.Register(kiteURL)
	if err != nil {
		t.Error(err)
	}
	defer m.Close()

	if kiteURL.String() != res.URL.String() {
		t.Error("register: got %s expected %s", res.URL.String(), kiteURL.String())
	}
}

func TestKontrol(t *testing.T) {
	t.Log("Setting up proxy")
	prx := proxy.New(conf.Copy(), "0.0.1", testkeys.Public, testkeys.Private)
	prx.Start()

	time.Sleep(1e9)

	// Start mathworker
	t.Log("Setting up mathworker")
	mathKite := kite.New("mathworker", "1.2.3")
	mathKite.Config = conf.Copy()
	mathKite.HandleFunc("square", Square)
	mathKite.Start()

	reg := registration.New(mathKite)
	go reg.RegisterToProxyAndKontrol()
	<-reg.ReadyNotify()

	// exp2 kite is the mathworker client
	t.Log("Setting up exp2 kite")
	exp2Kite := kite.New("exp2", "0.0.1")
	exp2Kite.Config = conf.Copy()

	query := protocol.KontrolQuery{
		Username:    exp2Kite.Kite().Username,
		Environment: exp2Kite.Kite().Environment,
		Name:        "mathworker",
		Version:     "~> 1.1",
	}

	// exp2 queries for mathkite
	t.Log("Querying for mathworkers")
	kites, err := exp2Kite.GetKites(query)
	if err != nil {
		t.Fatal(err)
	}

	if len(kites) == 0 {
		t.Fatal("No mathworker available")
	}

	// exp2 connectes to mathworker
	remoteMathWorker := kites[0]
	err = remoteMathWorker.Dial()
	if err != nil {
		t.Fatal("Cannot connect to remote mathworker", err)
	}

	// Test Kontrol.GetToken
	t.Logf("oldToken: %s", remoteMathWorker.Authentication.Key)
	newToken, err := exp2Kite.GetToken(&remoteMathWorker.Kite)
	if err != nil {
		t.Error(err)
	}
	t.Logf("newToken: %s", newToken)

	// Run "square" method
	response, err := remoteMathWorker.Tell("square", 2)
	if err != nil {
		t.Fatal(err)
	}

	var result int
	err = response.Unmarshal(&result)
	if err != nil {
		t.Fatal(err)
	}

	// Result must be "4"
	if result != 4 {
		t.Fatalf("Invalid result: %d", result)
	}

	events := make(chan *kite.Event, 3)

	// Test WatchKites
	t.Log("calling  watchkites")
	watcher, err := exp2Kite.WatchKites(query, func(e *kite.Event, err *kite.Error) {
		if err != nil {
			t.Fatal(err)
		}

		t.Logf("Event.Action: %s Event.Kite.ID: %s", e.Action, e.Kite.ID)
		events <- e
	})
	if err != nil {
		t.Fatalf("Cannot watch: %s", err.Error())
	}

	// First event must be register event because math worker is already running
	select {
	case e := <-events:
		if e.Action != protocol.Register {
			t.Fatalf("unexpected action: %s", e.Action)
		}
	case <-time.After(time.Second):
		t.Fatal("timeout")
	}

	t.Log("closing mathworker")
	mathKite.Close()

	// We must get Deregister event
	select {
	case e := <-events:
		if e.Action != protocol.Deregister {
			t.Fatalf("unexpected action: %s", e.Action)
		}
	case <-time.After(time.Second):
		t.Fatal("timeout")
	}

	// Start a new mathworker kite
	t.Log("Setting up mathworker2")
	mathKite2 := kite.New("mathworker", "1.2.3")
	mathKite2.Config = conf.Copy()
	mathKite2.Start()

	reg2 := registration.New(mathKite2)
	go reg2.RegisterToProxyAndKontrol()
	<-reg2.ReadyNotify()

	// We must get Register event
	select {
	case e := <-events:
		if e.Action != protocol.Register {
			t.Fatalf("unexpected action: %s", e.Action)
		}
	case <-time.After(time.Second):
		t.Fatal("timeout")
	}

	err = watcher.Cancel()
	if err != nil {
		t.Fatal(err)
	}

	// We must not get any event after cancelling the watcher
	select {
	case e := <-events:
		t.Fatalf("unexpected event: %s", e)
	case <-time.After(time.Second):
	}
}

func Square(r *kite.Request) (interface{}, error) {
	a, err := r.Args.One().Float64()
	if err != nil {
		return nil, err
	}

	result := a * a

	fmt.Printf("Kite call, sending result '%f' back\n", result)

	return result, nil
}

func TestGetQueryKey(t *testing.T) {
	// This query is valid because there are no gaps between query fields.
	q := &protocol.KontrolQuery{
		Username:    "cenk",
		Environment: "production",
	}
	key, err := getQueryKey(q)
	if err != nil {
		t.Errorf(err.Error())
	}
	if key != "/cenk/production" {
		t.Errorf("Unexpected key: %s", key)
	}

	// This is wrong because Environment field is empty.
	// We can't make a query on etcd because wildcards are not allowed in paths.
	q = &protocol.KontrolQuery{
		Username: "cenk",
		Name:     "fs",
	}
	key, err = getQueryKey(q)
	if err == nil {
		t.Errorf("Error is expected")
	}
	if key != "" {
		t.Errorf("Key is not expected: %s", key)
	}

	// This is also wrong becaus each query must have a non-empty username field.
	q = &protocol.KontrolQuery{
		Environment: "production",
		Name:        "fs",
	}
	key, err = getQueryKey(q)
	if err == nil {
		t.Errorf("Error is expected")
	}
	if key != "" {
		t.Errorf("Key is not expected: %s", key)
	}
}
<|endoftext|>


<commit_before>package commands

const (
	CommandClassDoorLock             = 0x62
	CommandClassThermostatSetpointV3 = 0x43
)

const (
	DoorLockOperationSet  = 0x01
	ThermostatSetpointSet = 0x01
)

const (
	DoorLockStatusUnlocked = 0x00
	DoorLockStatusLocked   = 0xFF
)
<commit_msg>add some new constants for the security frame<commit_after>package commands

const (
	CommandClassDoorLock             = 0x62
	CommandClassThermostatSetpointV3 = 0x43
	CommandClassSecurity             = 0x98
)

const (
	// Door lock
	DoorLockOperationSet = 0x01

	// Thermostat
	ThermostatSetpointSet = 0x01

	// Security
	NetworkKeySet                        = 0x06
	NetworkKeyVerify                     = 0x07
	SecurityCommandsSupportedGet         = 0x02
	SecurityMessageEncapsulation         = 0x81
	SecurityMessageEncapsulationNonceGet = 0xC1
	SecurityNonceGet                     = 0x40
	SecurityNonceReport                  = 0x80
	SecuritySchemeGet                    = 0x04
)

const (
	DoorLockStatusUnlocked = 0x00
	DoorLockStatusLocked   = 0xFF
)
<|endoftext|>


<commit_before><commit_msg>don't log config<commit_after><|endoftext|>


<commit_before>// Copyright 2016 The Serviced Authors.
// 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 service

import (
	"errors"
	"path"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/control-center/serviced/coordinator/client"
	"github.com/control-center/serviced/domain/host"
	"github.com/control-center/serviced/domain/service"
)

// HostRegistryListener monitors the availability of hosts within a pool
// by watching for children within the path
// /pools/POOLID/hosts/HOSTID/online
type HostRegistryListener struct {
	conn     client.Connection
	poolid   string
	isOnline chan struct{}
	handler  VirtualIPUnassignmentHandler
}

// VirtualIPUnassignmentHandler will handle unassigning virtual IPs for a host.  UnassignAll should
// be called when a host is about to go offline.
type VirtualIPUnassignmentHandler interface {
	UnassignAll(poolID, hostID string) error
}

// NewHostRegistryListener instantiates a new host registry listener
func NewHostRegistryListener(poolid string, handler VirtualIPUnassignmentHandler) *HostRegistryListener {
	return &HostRegistryListener{
		poolid:   poolid,
		handler:  handler,
		isOnline: make(chan struct{}),
	}
}

func (h *HostRegistryListener) SetConnection(conn client.Connection) {
	h.conn = conn
}

func (h *HostRegistryListener) GetPath(nodes ...string) string {
	base := append([]string{"/pools", h.poolid, "hosts"}, nodes...)
	return path.Join(base...)
}

func (h *HostRegistryListener) Ready() error {
	return nil
}

func (h *HostRegistryListener) Done() {
}

func (h *HostRegistryListener) PostProcess(p map[string]struct{}) {
}

func (h *HostRegistryListener) Spawn(cancel <-chan interface{}, hostid string) {
	logger := plog.WithFields(log.Fields{
		"poolid": h.poolid,
		"hostid": hostid,
	})

	// set up the connection timeout timer and track outage times.
	isOnline := false
	outage := time.Now()

	firstTimeout := true
	offlineTimer := time.NewTimer(h.getTimeout())
	defer offlineTimer.Stop()
	onlineTimer := time.NewTimer(0)
	defer onlineTimer.Stop()

	// set up cancellable on coordinator events
	stop := make(chan struct{})
	defer func() { close(stop) }()

	for {

		// does the host exist?
		// path: /pools/<poolid>/hosts/<hostid>
		isAvailable, availEv, err := h.conn.ExistsW(h.GetPath(hostid), stop)
		if err != nil {

			logger.WithError(err).Error("Could not look up host")
			return
		}
		if !isAvailable {

			logger.Debug("Host does not exist; stopping listener")
			return
		}

		// check to see if the host is up
		// path: /pools/<poolid>/hosts/<hostid>/online
		var ch []string
		onlinepth := h.GetPath(hostid, "online")
		isAvailable, onlineEv, err := h.conn.ExistsW(onlinepth, stop)
		if err != nil {

			logger.WithError(err).Error("Could not check online status of host")
			return
		}
		if isAvailable {
			// host is online, check the network availability
			ch, onlineEv, err = h.conn.ChildrenW(onlinepth, stop)
			if err != nil {

				logger.WithError(err).Error("Could not verify online status of host")
				return
			}
			isAvailable = len(ch) > 0
		} else {
			// host has shut down cleanly, ensure all nodes are cleaned up
			count := DeleteHostStates(h.conn, h.poolid, hostid)
			if count > 0 {
				logger.WithField("unscheduled", count).Warn("Host reported shutdown; cleaned up orphaned nodes")
			} else {
				logger.Debug("Host reported shutdown")
			}
		}

		// update the node's online status
		if !isAvailable && isOnline {

			// host is down, begin the countdown
			logger.Debug("Host is not available, starting network timeout")

			isOnline = false
			outage = time.Now()

			firstTimeout = true
			offlineTimer.Stop()
			offlineTimer = time.NewTimer(h.getTimeout())

		} else if isAvailable && !isOnline {

			// host is up, halt the countdown
			logger.WithField("outage", time.Since(outage)).Info("Host is online")
			isOnline = true
		}

		// find out if the host can receive new services
		// path: /pools/<poolid>/hosts/<hostid>/locked
		lockpth := h.GetPath(hostid, "locked")
		isLocked, lockev, err := h.conn.ExistsW(lockpth, stop)
		if err != nil {

			logger.WithError(err).Error("Could not check locked status of host")
			return
		}
		if isLocked {
			ch, lockev, err = h.conn.ChildrenW(lockpth, stop)
			if err != nil {

				logger.WithError(err).Error("Could not verify locked status of host")
				return
			}
			isLocked = len(ch) > 0
		}

		// clean up invalid states and find out if the host is running anything
		// path: /pools/<poolid>/hosts/<hostid>/instances
		if err := CleanHostStates(h.conn, h.poolid, hostid); err != nil {

			logger.WithError(err).Error("Could not clean states on host")
			return
		}
		ch, err = h.conn.Children(h.GetPath(hostid, "instances"))
		if err != nil && err != client.ErrNoNode {

			logger.WithError(err).Error("Could not look up instances on host")
			return
		}
		isRunning := len(ch) > 0

		eventLogger := plog.WithFields(log.Fields{
			"poolid":    h.poolid,
			"hostid":    hostid,
			"isonline":  isOnline,
			"islocked":  isLocked,
			"isrunning": isRunning,
		})
		eventLogger.Debug("Waiting for host event")

		if isOnline {

			if !isLocked {

				// If the host is online, try to tell someone who cares.
				// Expectedly, this is not something that should be in high
				// demand.
				select {
				case h.isOnline <- struct{}{}:
				case <-lockev:
				case <-availEv:
				case <-onlineEv:
				case <-cancel:
					return
				}

			} else {

				// If the host is locked, then we cannot advertise scheduling
				// on this host, so rather we should wait until the lock is
				// freed.
				select {
				case <-lockev:
				case <-availEv:
				case <-onlineEv:
				case <-cancel:
					return
				}
			}

		} else if !isRunning {

			// I only care about an outage if I am running instances.  If I am
			// offline and not running instances, nothing will get scheduled to
			// me anyway.
			select {
			case <-availEv:
			case <-onlineEv:
			case <-cancel:
				return
			}

		} else {

			// If this is a network outage, not all hosts may appear offline at
			// the same time, so lets allow it to quiesce before trying to
			// reschedule.
			select {
			case <-offlineTimer.C:
				offlineTimer.Reset(0)

				// This may be a genuine host outage.  Alert when a host is
				// available.
				select {
				case <-h.isOnline:

					// Reset the online timer in case this is an outage and
					// we need to allow the system quiesce as it is coming back
					// online.
					if firstTimeout {
						onlineTimer.Stop()
						onlineTimer = time.NewTimer(h.getTimeout())
						firstTimeout = false
					}

					select {
					case <-onlineTimer.C:
						onlineTimer.Reset(0)

						// We have exceeded the wait timeout, so reschedule as
						// soon as possible.

						select {
						case <-h.isOnline:
							// Only reschedule services without address
							// assignments.

							h.handler.UnassignAll(h.poolid, hostid)

							count := DeleteHostStatesWhen(h.conn, h.poolid, hostid, func(s *State) bool {
								return s.DesiredState == service.SVCStop || !s.Static
							})
							logger.WithField("unscheduled", count).Warn("Host is experiencing an outage.  Cleaned up orphaned nodes")

							// To prevent a tight loop, wait for something to
							// happen.
							select {
							case <-availEv:
							case <-onlineEv:
							case <-cancel:
								return
							}

						case <-availEv:
						case <-onlineEv:
						case <-cancel:
							return
						}
					case <-availEv:
					case <-onlineEv:
					case <-cancel:
						return
					}
				case <-availEv:
				case <-onlineEv:
				case <-cancel:
					return
				}
			case <-availEv:
			case <-onlineEv:
			case <-cancel:
				return
			}
		}

		close(stop)
		stop = make(chan struct{})
	}
}

// getTimeout returns the pool connection timeout.  Returns 0 if data cannot be
// acquired.
func (h *HostRegistryListener) getTimeout() time.Duration {
	logger := plog.WithField("poolid", h.poolid)

	var p PoolNode
	if err := h.conn.Get("/pools/"+h.poolid, &p); err != nil {
		logger.WithError(err).Warn("Could not look up resource pool for connection timeout")
		return 0
	}

	return p.GetConnectionTimeout()
}

// GetRegisteredHosts returns a list of hosts that are active.  If there are
// zero active hosts, then it will wait until at least one host is available.
func (h *HostRegistryListener) GetRegisteredHosts(cancel <-chan interface{}) ([]host.Host, error) {
	logger := plog.WithField("poolid", h.poolid)

	var conn client.Connection
	if conn = h.conn; conn == nil {
		return nil, errors.New("connection is not initialized")
	}

	for {
		hosts, err := GetRegisteredHostsForPool(conn, h.poolid)
		if err != nil {
			return nil, err
		}

		if count := len(hosts); count > 0 {
			return hosts, nil
		}

		logger.Warn("No active hosts registered, waiting")

		select {
		case <-h.isOnline:
			logger.Info("At least one active host detected, checking")
		case <-cancel:
			return []host.Host{}, nil
		}
	}
}

func GetRegisteredHostsForPool(conn client.Connection, poolID string) ([]host.Host, error) {
	logger := plog.WithField("poolid", poolID)

	hosts := []host.Host{}
	for {
		hostids, err := GetCurrentHosts(conn, poolID)
		if err != nil {
			return nil, err
		}

		for _, hostid := range hostids {
			hstlog := logger.WithField("hostid", hostid)

			// only return hosts that are not locked
			ch, err := conn.Children(Base().Pools().ID(poolID).Hosts().ID(hostid).Locked().Path())
			if err != nil && err != client.ErrNoNode {

				hstlog.WithError(err).Debug("Could not check if host is locked")

				// TODO: wrap error?
				return nil, err
			}

			isLocked := len(ch) > 0
			if !isLocked {
				hdat := host.Host{}
				err := conn.Get(Base().Pools().ID(poolID).Hosts().ID(hostid).Path(), &HostNode{Host: &hdat})
				if err == client.ErrNoNode {
					continue
				} else if err != nil {

					hstlog.WithError(err).Debug("Could not load host")

					// TODO: wrap error?
					return nil, err
				}
				hosts = append(hosts, hdat)
			}
		}

		logger.WithField("hostcount", len(hosts)).Debug("Loaded active hosts")

		return hosts, nil
	}
}

// RegisterHost persists a registered host to the coordinator.  This is managed
// by the worker node, so it is expected that the connection will be pre-loaded
// with the path to the resource pool.
func RegisterHost(cancel <-chan interface{}, conn client.Connection, hostid string) error {
	logger := plog.WithField("hostid", hostid)

	pth := path.Join("/hosts", hostid, "online")

	// clean up ephemeral nodes on exit
	defer func() {
		ch, _ := conn.Children(pth)
		for _, n := range ch {
			conn.Delete(path.Join(pth, n))
		}
	}()

	// set up cancellable on event watcher
	stop := make(chan struct{})
	defer func() { close(stop) }()
	for {

		// monitor the parent node
		regok, regev, err := conn.ExistsW(path.Dir(pth), stop)
		if err != nil {

			logger.WithError(err).Debug("Could not check if host is registered")

			// TODO: wrap error?
			return err

		} else if !regok {

			logger.Warn("Host not found; system is idle")
			select {
			case <-regev:
			case <-cancel:
				return nil
			}
			close(stop)
			stop = make(chan struct{})
			continue
		}

		// the host goes online
		if err := conn.CreateIfExists(pth, &client.Dir{}); err == client.ErrNoNode {

			logger.Warn("Host is not registered; system is idle")
			select {
			case <-regev:
			case <-cancel:
				return nil
			}
			close(stop)
			stop = make(chan struct{})
			continue
		} else if err != nil && err != client.ErrNodeExists {

			logger.WithError(err).Debug("Could not check if host is already registered")

			// TODO: wrap error?
			return err
		}

		// the host becomes active
		ch, ev, err := conn.ChildrenW(pth, stop)
		if err == client.ErrNoNode {

			logger.Warn("Host is not active; system is idle")
			select {
			case <-regev:
			case <-cancel:
				return nil
			}
			close(stop)
			stop = make(chan struct{})
			continue
		} else if err != nil {

			logger.WithError(err).Debug("Could not check if host is active")

			// TODO: wrap error?
			return err
		}

		// register the host if it isn't showing up as active
		if len(ch) == 0 {
			// Need to give the ephemeral a node name, despite the name
			// changing when it is written to the coordinator.
			_, err = conn.CreateEphemeralIfExists(path.Join(pth, hostid), &client.Dir{})
			if err != nil {

				logger.WithError(err).Debug("Could not register host")

				// TODO: wrap error?
				return err
			}
		}

		select {
		case <-regev:
		case <-ev:
		case <-cancel:
			return nil
		}
		close(stop)
		stop = make(chan struct{})
	}
}
<commit_msg>CC-4082 Host disconnect should reflect timeout duration int he logs<commit_after>// Copyright 2016 The Serviced Authors.
// 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 service

import (
	"errors"
	"path"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/control-center/serviced/coordinator/client"
	"github.com/control-center/serviced/domain/host"
	"github.com/control-center/serviced/domain/service"
)

// HostRegistryListener monitors the availability of hosts within a pool
// by watching for children within the path
// /pools/POOLID/hosts/HOSTID/online
type HostRegistryListener struct {
	conn     client.Connection
	poolid   string
	isOnline chan struct{}
	handler  VirtualIPUnassignmentHandler
}

// VirtualIPUnassignmentHandler will handle unassigning virtual IPs for a host.  UnassignAll should
// be called when a host is about to go offline.
type VirtualIPUnassignmentHandler interface {
	UnassignAll(poolID, hostID string) error
}

// NewHostRegistryListener instantiates a new host registry listener
func NewHostRegistryListener(poolid string, handler VirtualIPUnassignmentHandler) *HostRegistryListener {
	return &HostRegistryListener{
		poolid:   poolid,
		handler:  handler,
		isOnline: make(chan struct{}),
	}
}

func (h *HostRegistryListener) SetConnection(conn client.Connection) {
	h.conn = conn
}

func (h *HostRegistryListener) GetPath(nodes ...string) string {
	base := append([]string{"/pools", h.poolid, "hosts"}, nodes...)
	return path.Join(base...)
}

func (h *HostRegistryListener) Ready() error {
	return nil
}

func (h *HostRegistryListener) Done() {
}

func (h *HostRegistryListener) PostProcess(p map[string]struct{}) {
}

func (h *HostRegistryListener) Spawn(cancel <-chan interface{}, hostid string) {
	logger := plog.WithFields(log.Fields{
		"poolid": h.poolid,
		"hostid": hostid,
	})

	// set up the connection timeout timer and track outage times.
	isOnline := false
	outage := time.Now()

	firstTimeout := true
	offlineTimer := time.NewTimer(h.getTimeout())
	defer offlineTimer.Stop()
	onlineTimer := time.NewTimer(0)
	defer onlineTimer.Stop()

	// set up cancellable on coordinator events
	stop := make(chan struct{})
	defer func() { close(stop) }()

	var connectionTimeout time.Duration

	for {

		// does the host exist?
		// path: /pools/<poolid>/hosts/<hostid>
		isAvailable, availEv, err := h.conn.ExistsW(h.GetPath(hostid), stop)
		if err != nil {

			logger.WithError(err).Error("Could not look up host")
			return
		}
		if !isAvailable {

			logger.Debug("Host does not exist; stopping listener")
			return
		}

		// check to see if the host is up
		// path: /pools/<poolid>/hosts/<hostid>/online
		var ch []string
		onlinepth := h.GetPath(hostid, "online")
		isAvailable, onlineEv, err := h.conn.ExistsW(onlinepth, stop)
		if err != nil {

			logger.WithError(err).Error("Could not check online status of host")
			return
		}
		if isAvailable {
			// host is online, check the network availability
			ch, onlineEv, err = h.conn.ChildrenW(onlinepth, stop)
			if err != nil {

				logger.WithError(err).Error("Could not verify online status of host")
				return
			}
			isAvailable = len(ch) > 0
		} else {
			// host has shut down cleanly, ensure all nodes are cleaned up
			count := DeleteHostStates(h.conn, h.poolid, hostid)
			if count > 0 {
				logger.WithField("unscheduled", count).Warn("Host reported shutdown; cleaned up orphaned nodes")
			} else {
				logger.Debug("Host reported shutdown")
			}
		}

		// update the node's online status
		if !isAvailable && isOnline {

			// host is down, begin the countdown
			logger.Debug("Host is not available, starting network timeout")

			isOnline = false
			outage = time.Now()

			connectionTimeout = h.getTimeout()

			firstTimeout = true
			offlineTimer.Stop()
			offlineTimer = time.NewTimer(connectionTimeout)

		} else if isAvailable && !isOnline {

			// host is up, halt the countdown
			logger.WithField("outage", time.Since(outage)).Info("Host is online")
			isOnline = true
		}

		// find out if the host can receive new services
		// path: /pools/<poolid>/hosts/<hostid>/locked
		lockpth := h.GetPath(hostid, "locked")
		isLocked, lockev, err := h.conn.ExistsW(lockpth, stop)
		if err != nil {

			logger.WithError(err).Error("Could not check locked status of host")
			return
		}
		if isLocked {
			ch, lockev, err = h.conn.ChildrenW(lockpth, stop)
			if err != nil {

				logger.WithError(err).Error("Could not verify locked status of host")
				return
			}
			isLocked = len(ch) > 0
		}

		// clean up invalid states and find out if the host is running anything
		// path: /pools/<poolid>/hosts/<hostid>/instances
		if err := CleanHostStates(h.conn, h.poolid, hostid); err != nil {

			logger.WithError(err).Error("Could not clean states on host")
			return
		}
		ch, err = h.conn.Children(h.GetPath(hostid, "instances"))
		if err != nil && err != client.ErrNoNode {

			logger.WithError(err).Error("Could not look up instances on host")
			return
		}
		isRunning := len(ch) > 0

		eventLogger := plog.WithFields(log.Fields{
			"poolid":    h.poolid,
			"hostid":    hostid,
			"isonline":  isOnline,
			"islocked":  isLocked,
			"isrunning": isRunning,
		})
		eventLogger.Debug("Waiting for host event")

		if isOnline {

			if !isLocked {

				// If the host is online, try to tell someone who cares.
				// Expectedly, this is not something that should be in high
				// demand.
				select {
				case h.isOnline <- struct{}{}:
				case <-lockev:
				case <-availEv:
				case <-onlineEv:
				case <-cancel:
					return
				}

			} else {

				// If the host is locked, then we cannot advertise scheduling
				// on this host, so rather we should wait until the lock is
				// freed.
				select {
				case <-lockev:
				case <-availEv:
				case <-onlineEv:
				case <-cancel:
					return
				}
			}

		} else if !isRunning {

			// I only care about an outage if I am running instances.  If I am
			// offline and not running instances, nothing will get scheduled to
			// me anyway.
			select {
			case <-availEv:
			case <-onlineEv:
			case <-cancel:
				return
			}

		} else {

			// If this is a network outage, not all hosts may appear offline at
			// the same time, so lets allow it to quiesce before trying to
			// reschedule.
			select {
			case <-offlineTimer.C:
				offlineTimer.Reset(0)

				// This may be a genuine host outage.  Alert when a host is
				// available.
				select {
				case <-h.isOnline:

					// Reset the online timer in case this is an outage and
					// we need to allow the system quiesce as it is coming back
					// online.
					if firstTimeout {
						onlineTimer.Stop()
						onlineTimer = time.NewTimer(connectionTimeout)
						firstTimeout = false
					}

					select {
					case <-onlineTimer.C:
						onlineTimer.Reset(0)

						// We have exceeded the wait timeout, so reschedule as
						// soon as possible.

						select {
						case <-h.isOnline:
							// Only reschedule services without address
							// assignments.

							err := h.handler.UnassignAll(h.poolid, hostid)
							if err != nil {
								logger.WithError(err).Warn("Got an error while unassigning services.")
							}

							count := DeleteHostStatesWhen(h.conn, h.poolid, hostid, func(s *State) bool {
								return s.DesiredState == service.SVCStop || !s.Static
							})
							logger.WithField("unscheduled", count).WithField("connection_timeout", connectionTimeout).Warn("Host is experiencing an outage.  Cleaned up orphaned nodes.")

							// To prevent a tight loop, wait for something to
							// happen.
							select {
							case <-availEv:
							case <-onlineEv:
							case <-cancel:
								return
							}

						case <-availEv:
						case <-onlineEv:
						case <-cancel:
							return
						}
					case <-availEv:
					case <-onlineEv:
					case <-cancel:
						return
					}
				case <-availEv:
				case <-onlineEv:
				case <-cancel:
					return
				}
			case <-availEv:
			case <-onlineEv:
			case <-cancel:
				return
			}
		}

		close(stop)
		stop = make(chan struct{})
	}
}

// getTimeout returns the pool connection timeout.  Returns 0 if data cannot be
// acquired.
func (h *HostRegistryListener) getTimeout() time.Duration {
	logger := plog.WithField("poolid", h.poolid)

	var p PoolNode
	if err := h.conn.Get("/pools/"+h.poolid, &p); err != nil {
		logger.WithError(err).Warn("Could not look up resource pool for connection timeout")
		return 0
	}

	return p.GetConnectionTimeout()
}

// GetRegisteredHosts returns a list of hosts that are active.  If there are
// zero active hosts, then it will wait until at least one host is available.
func (h *HostRegistryListener) GetRegisteredHosts(cancel <-chan interface{}) ([]host.Host, error) {
	logger := plog.WithField("poolid", h.poolid)

	var conn client.Connection
	if conn = h.conn; conn == nil {
		return nil, errors.New("connection is not initialized")
	}

	for {
		hosts, err := GetRegisteredHostsForPool(conn, h.poolid)
		if err != nil {
			return nil, err
		}

		if count := len(hosts); count > 0 {
			return hosts, nil
		}

		logger.Warn("No active hosts registered, waiting")

		select {
		case <-h.isOnline:
			logger.Info("At least one active host detected, checking")
		case <-cancel:
			return []host.Host{}, nil
		}
	}
}

func GetRegisteredHostsForPool(conn client.Connection, poolID string) ([]host.Host, error) {
	logger := plog.WithField("poolid", poolID)

	hosts := []host.Host{}
	for {
		hostids, err := GetCurrentHosts(conn, poolID)
		if err != nil {
			return nil, err
		}

		for _, hostid := range hostids {
			hstlog := logger.WithField("hostid", hostid)

			// only return hosts that are not locked
			ch, err := conn.Children(Base().Pools().ID(poolID).Hosts().ID(hostid).Locked().Path())
			if err != nil && err != client.ErrNoNode {

				hstlog.WithError(err).Debug("Could not check if host is locked")

				// TODO: wrap error?
				return nil, err
			}

			isLocked := len(ch) > 0
			if !isLocked {
				hdat := host.Host{}
				err := conn.Get(Base().Pools().ID(poolID).Hosts().ID(hostid).Path(), &HostNode{Host: &hdat})
				if err == client.ErrNoNode {
					continue
				} else if err != nil {

					hstlog.WithError(err).Debug("Could not load host")

					// TODO: wrap error?
					return nil, err
				}
				hosts = append(hosts, hdat)
			}
		}

		logger.WithField("hostcount", len(hosts)).Debug("Loaded active hosts")

		return hosts, nil
	}
}

// RegisterHost persists a registered host to the coordinator.  This is managed
// by the worker node, so it is expected that the connection will be pre-loaded
// with the path to the resource pool.
func RegisterHost(cancel <-chan interface{}, conn client.Connection, hostid string) error {
	logger := plog.WithField("hostid", hostid)

	pth := path.Join("/hosts", hostid, "online")

	// clean up ephemeral nodes on exit
	defer func() {
		ch, _ := conn.Children(pth)
		for _, n := range ch {
			conn.Delete(path.Join(pth, n))
		}
	}()

	// set up cancellable on event watcher
	stop := make(chan struct{})
	defer func() { close(stop) }()
	for {

		// monitor the parent node
		regok, regev, err := conn.ExistsW(path.Dir(pth), stop)
		if err != nil {

			logger.WithError(err).Debug("Could not check if host is registered")

			// TODO: wrap error?
			return err

		} else if !regok {

			logger.Warn("Host not found; system is idle")
			select {
			case <-regev:
			case <-cancel:
				return nil
			}
			close(stop)
			stop = make(chan struct{})
			continue
		}

		// the host goes online
		if err := conn.CreateIfExists(pth, &client.Dir{}); err == client.ErrNoNode {

			logger.Warn("Host is not registered; system is idle")
			select {
			case <-regev:
			case <-cancel:
				return nil
			}
			close(stop)
			stop = make(chan struct{})
			continue
		} else if err != nil && err != client.ErrNodeExists {

			logger.WithError(err).Debug("Could not check if host is already registered")

			// TODO: wrap error?
			return err
		}

		// the host becomes active
		ch, ev, err := conn.ChildrenW(pth, stop)
		if err == client.ErrNoNode {

			logger.Warn("Host is not active; system is idle")
			select {
			case <-regev:
			case <-cancel:
				return nil
			}
			close(stop)
			stop = make(chan struct{})
			continue
		} else if err != nil {

			logger.WithError(err).Debug("Could not check if host is active")

			// TODO: wrap error?
			return err
		}

		// register the host if it isn't showing up as active
		if len(ch) == 0 {
			// Need to give the ephemeral a node name, despite the name
			// changing when it is written to the coordinator.
			_, err = conn.CreateEphemeralIfExists(path.Join(pth, hostid), &client.Dir{})
			if err != nil {

				logger.WithError(err).Debug("Could not register host")

				// TODO: wrap error?
				return err
			}
		}

		select {
		case <-regev:
		case <-ev:
		case <-cancel:
			return nil
		}
		close(stop)
		stop = make(chan struct{})
	}
}
<|endoftext|>