<commit_before>/*
Copyright 2014 The Kubernetes 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 runtime

import (
	"fmt"
	"runtime"
	"sync"
	"time"

	"k8s.io/klog"
)

var (
	// ReallyCrash controls the behavior of HandleCrash and now defaults
	// true. It's still exposed so components can optionally set to false
	// to restore prior behavior.
	ReallyCrash = true
)

// PanicHandlers is a list of functions which will be invoked when a panic happens.
var PanicHandlers = []func(interface{}){logPanic}

// HandleCrash simply catches a crash and logs an error. Meant to be called via
// defer.  Additional context-specific handlers can be provided, and will be
// called in case of panic.  HandleCrash actually crashes, after calling the
// handlers and logging the panic message.
//
// TODO: remove this function. We are switching to a world where it's safe for
// apiserver to panic, since it will be restarted by kubelet. At the beginning
// of the Kubernetes project, nothing was going to restart apiserver and so
// catching panics was important. But it's actually much simpler for monitoring
// software if we just exit when an unexpected panic happens.
func HandleCrash(additionalHandlers ...func(interface{})) {
	if r := recover(); r != nil {
		for _, fn := range PanicHandlers {
			fn(r)
		}
		for _, fn := range additionalHandlers {
			fn(r)
		}
		if ReallyCrash {
			// Actually proceed to panic.
			panic(r)
		}
	}
}

// logPanic logs the caller tree when a panic occurs.
func logPanic(r interface{}) {
	// Same as stdlib http server code. Manually allocate stack trace buffer size
	// to prevent excessively large logs
	const size = 64 << 10
	stacktrace := make([]byte, size)
	stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]
	if _, ok := r.(string); ok {
		klog.Errorf("Observed a panic: %s\n%s", r, stacktrace)
	} else {
		klog.Errorf("Observed a panic: %#v (%v)\n%s", r, r, stacktrace)
	}
}

// ErrorHandlers is a list of functions which will be invoked when an unreturnable
// error occurs.
// TODO(lavalamp): for testability, this and the below HandleError function
// should be packaged up into a testable and reusable object.
var ErrorHandlers = []func(error){
	logError,
	(&rudimentaryErrorBackoff{
		lastErrorTime: time.Now(),
		// 1ms was the number folks were able to stomach as a global rate limit.
		// If you need to log errors more than 1000 times a second you
		// should probably consider fixing your code instead. :)
		minPeriod: time.Millisecond,
	}).OnError,
}

// HandlerError is a method to invoke when a non-user facing piece of code cannot
// return an error and needs to indicate it has been ignored. Invoking this method
// is preferable to logging the error - the default behavior is to log but the
// errors may be sent to a remote server for analysis.
func HandleError(err error) {
	// this is sometimes called with a nil error.  We probably shouldn't fail and should do nothing instead
	if err == nil {
		return
	}

	for _, fn := range ErrorHandlers {
		fn(err)
	}
}

// logError prints an error with the call stack of the location it was reported
func logError(err error) {
	klog.ErrorDepth(2, err)
}

type rudimentaryErrorBackoff struct {
	minPeriod time.Duration // immutable
	// TODO(lavalamp): use the clock for testability. Need to move that
	// package for that to be accessible here.
	lastErrorTimeLock sync.Mutex
	lastErrorTime     time.Time
}

// OnError will block if it is called more often than the embedded period time.
// This will prevent overly tight hot error loops.
func (r *rudimentaryErrorBackoff) OnError(error) {
	r.lastErrorTimeLock.Lock()
	defer r.lastErrorTimeLock.Unlock()
	d := time.Since(r.lastErrorTime)
	if d < r.minPeriod {
		// If the time moves backwards for any reason, do nothing
		time.Sleep(r.minPeriod - d)
	}
	r.lastErrorTime = time.Now()
}

// GetCaller returns the caller of the function that calls it.
func GetCaller() string {
	var pc [1]uintptr
	runtime.Callers(3, pc[:])
	f := runtime.FuncForPC(pc[0])
	if f == nil {
		return fmt.Sprintf("Unable to find caller")
	}
	return f.Name()
}

// RecoverFromPanic replaces the specified error with an error containing the
// original error, and  the call tree when a panic occurs. This enables error
// handlers to handle errors and panics the same way.
func RecoverFromPanic(err *error) {
	if r := recover(); r != nil {
		// Same as stdlib http server code. Manually allocate stack trace buffer size
		// to prevent excessively large logs
		const size = 64 << 10
		stacktrace := make([]byte, size)
		stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]

		*err = fmt.Errorf(
			"recovered from panic %q. (err=%v) Call stack:\n%s",
			r,
			*err,
			stacktrace)
	}
}

// Must panics on non-nil errors.  Useful to handling programmer level errors.
func Must(err error) {
	if err != nil {
		panic(err)
	}
}
<commit_msg>Update misleading comemnts for HandleCrash<commit_after>/*
Copyright 2014 The Kubernetes 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 runtime

import (
	"fmt"
	"runtime"
	"sync"
	"time"

	"k8s.io/klog"
)

var (
	// ReallyCrash controls the behavior of HandleCrash and now defaults
	// true. It's still exposed so components can optionally set to false
	// to restore prior behavior.
	ReallyCrash = true
)

// PanicHandlers is a list of functions which will be invoked when a panic happens.
var PanicHandlers = []func(interface{}){logPanic}

// HandleCrash simply catches a crash and logs an error. Meant to be called via
// defer.  Additional context-specific handlers can be provided, and will be
// called in case of panic.  HandleCrash actually crashes, after calling the
// handlers and logging the panic message.
//
// E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully.
func HandleCrash(additionalHandlers ...func(interface{})) {
	if r := recover(); r != nil {
		for _, fn := range PanicHandlers {
			fn(r)
		}
		for _, fn := range additionalHandlers {
			fn(r)
		}
		if ReallyCrash {
			// Actually proceed to panic.
			panic(r)
		}
	}
}

// logPanic logs the caller tree when a panic occurs.
func logPanic(r interface{}) {
	// Same as stdlib http server code. Manually allocate stack trace buffer size
	// to prevent excessively large logs
	const size = 64 << 10
	stacktrace := make([]byte, size)
	stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]
	if _, ok := r.(string); ok {
		klog.Errorf("Observed a panic: %s\n%s", r, stacktrace)
	} else {
		klog.Errorf("Observed a panic: %#v (%v)\n%s", r, r, stacktrace)
	}
}

// ErrorHandlers is a list of functions which will be invoked when an unreturnable
// error occurs.
// TODO(lavalamp): for testability, this and the below HandleError function
// should be packaged up into a testable and reusable object.
var ErrorHandlers = []func(error){
	logError,
	(&rudimentaryErrorBackoff{
		lastErrorTime: time.Now(),
		// 1ms was the number folks were able to stomach as a global rate limit.
		// If you need to log errors more than 1000 times a second you
		// should probably consider fixing your code instead. :)
		minPeriod: time.Millisecond,
	}).OnError,
}

// HandlerError is a method to invoke when a non-user facing piece of code cannot
// return an error and needs to indicate it has been ignored. Invoking this method
// is preferable to logging the error - the default behavior is to log but the
// errors may be sent to a remote server for analysis.
func HandleError(err error) {
	// this is sometimes called with a nil error.  We probably shouldn't fail and should do nothing instead
	if err == nil {
		return
	}

	for _, fn := range ErrorHandlers {
		fn(err)
	}
}

// logError prints an error with the call stack of the location it was reported
func logError(err error) {
	klog.ErrorDepth(2, err)
}

type rudimentaryErrorBackoff struct {
	minPeriod time.Duration // immutable
	// TODO(lavalamp): use the clock for testability. Need to move that
	// package for that to be accessible here.
	lastErrorTimeLock sync.Mutex
	lastErrorTime     time.Time
}

// OnError will block if it is called more often than the embedded period time.
// This will prevent overly tight hot error loops.
func (r *rudimentaryErrorBackoff) OnError(error) {
	r.lastErrorTimeLock.Lock()
	defer r.lastErrorTimeLock.Unlock()
	d := time.Since(r.lastErrorTime)
	if d < r.minPeriod {
		// If the time moves backwards for any reason, do nothing
		time.Sleep(r.minPeriod - d)
	}
	r.lastErrorTime = time.Now()
}

// GetCaller returns the caller of the function that calls it.
func GetCaller() string {
	var pc [1]uintptr
	runtime.Callers(3, pc[:])
	f := runtime.FuncForPC(pc[0])
	if f == nil {
		return fmt.Sprintf("Unable to find caller")
	}
	return f.Name()
}

// RecoverFromPanic replaces the specified error with an error containing the
// original error, and  the call tree when a panic occurs. This enables error
// handlers to handle errors and panics the same way.
func RecoverFromPanic(err *error) {
	if r := recover(); r != nil {
		// Same as stdlib http server code. Manually allocate stack trace buffer size
		// to prevent excessively large logs
		const size = 64 << 10
		stacktrace := make([]byte, size)
		stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]

		*err = fmt.Errorf(
			"recovered from panic %q. (err=%v) Call stack:\n%s",
			r,
			*err,
			stacktrace)
	}
}

// Must panics on non-nil errors.  Useful to handling programmer level errors.
func Must(err error) {
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


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

package addresser

import (
	"time"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils"

	apiWatcher "github.com/juju/juju/api/watcher"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/instance"
	"github.com/juju/juju/network"
	"github.com/juju/juju/state"
	"github.com/juju/juju/worker"
)

var logger = loggo.GetLogger("juju.worker.addresser")

var shortAttempt = utils.AttemptStrategy{
	Total: 5 * time.Second,
	Delay: 300 * time.Millisecond,
}

type releaser interface {
	// ReleaseAddress has the same signature as the same method in the
	// environs.Networking interface.
	ReleaseAddress(instance.Id, network.Id, network.Address) error
}

// stateAddresser defines the State methods used by the addresserHandler
type stateAddresser interface {
	DeadIPAddresses() ([]*state.IPAddress, error)
	EnvironConfig() (*config.Config, error)
	IPAddress(string) (*state.IPAddress, error)
	Machine(string) (*state.Machine, error)
	WatchIPAddresses() state.StringsWatcher
}

type addresserHandler struct {
	st       stateAddresser
	releaser releaser
}

// NewWorker returns a worker that keeps track of
// IP address lifecycles, releaseing and removing Dead addresses.
func NewWorker(st stateAddresser) (worker.Worker, error) {
	config, err := st.EnvironConfig()
	if err != nil {
		return nil, errors.Trace(err)
	}
	environ, err := environs.New(config)
	if err != nil {
		return nil, errors.Trace(err)
	}
	netEnviron, ok := environs.SupportsNetworking(environ)
	if !ok {
		return nil, errors.New("environment does not support networking")
	}
	a := newWorkerWithReleaser(st, netEnviron)
	return a, nil
}

func newWorkerWithReleaser(st stateAddresser, releaser releaser) worker.Worker {
	a := &addresserHandler{
		st:       st,
		releaser: releaser,
	}
	w := worker.NewStringsWorker(a)
	return w
}

// Handle is part of the StringsWorker interface.
func (a *addresserHandler) Handle(ids []string) error {
	for _, id := range ids {
		logger.Debugf("received notification about address %v", id)
		addr, err := a.st.IPAddress(id)
		if err != nil {
			if errors.IsNotFound(err) {
				logger.Debugf("address %v is missing", id)
				continue
			}
			return err
		}
		if addr.Life() != state.Dead {
			logger.Debugf("address %v is not Dead, life %q", id, addr.Life())
			continue
		}
		err = a.releaseIPAddress(addr)
		if err != nil {
			return err
		}
		err = addr.Remove()
		if err != nil {
			return err
		}
	}
	return nil
}

func (a *addresserHandler) releaseIPAddress(addr *state.IPAddress) (err error) {
	defer errors.DeferredAnnotatef(&err, "failed to release address %v", addr.Value())
	var machine *state.Machine
	logger.Debugf("attempting to release dead address %#v", addr.Value())
	machine, err = a.st.Machine(addr.MachineId())
	if err != nil {
		return errors.Annotatef(err, "cannot get allocated machine %q", addr.MachineId())
	}

	var instId instance.Id
	instId, err = machine.InstanceId()
	if err != nil {
		return errors.Annotatef(err, "cannot get machine %q instance ID", addr.MachineId())
	}

	netId := network.Id(addr.SubnetId())
	for attempt := shortAttempt.Start(); attempt.Next(); {
		err = a.releaser.ReleaseAddress(instId, netId, addr.Address())
		if err == nil {
			return nil
		}
	}
	// Don't remove the address from state so we
	// can retry releasing the address later.
	logger.Warningf("cannot release address %q: %v (will retry)", addr.Value(), err)
	return errors.Trace(err)
}

// SetUp is part of the StringsWorker interface.
func (a *addresserHandler) SetUp() (apiWatcher.StringsWatcher, error) {
	w := a.st.WatchIPAddresses()
	return w, nil
}

// TearDown is part of the StringsWorker interface.
func (a *addresserHandler) TearDown() error {
	return nil
}
<commit_msg>Minor tweaks from review<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package addresser

import (
	"github.com/juju/errors"
	"github.com/juju/loggo"

	apiWatcher "github.com/juju/juju/api/watcher"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/instance"
	"github.com/juju/juju/network"
	"github.com/juju/juju/provider/common"
	"github.com/juju/juju/state"
	"github.com/juju/juju/worker"
)

var logger = loggo.GetLogger("juju.worker.addresser")

type releaser interface {
	// ReleaseAddress has the same signature as the same method in the
	// environs.Networking interface.
	ReleaseAddress(instance.Id, network.Id, network.Address) error
}

// stateAddresser defines the State methods used by the addresserHandler
type stateAddresser interface {
	DeadIPAddresses() ([]*state.IPAddress, error)
	EnvironConfig() (*config.Config, error)
	IPAddress(string) (*state.IPAddress, error)
	Machine(string) (*state.Machine, error)
	WatchIPAddresses() state.StringsWatcher
}

type addresserHandler struct {
	st       stateAddresser
	releaser releaser
}

// NewWorker returns a worker that keeps track of
// IP address lifecycles, releaseing and removing Dead addresses.
func NewWorker(st stateAddresser) (worker.Worker, error) {
	config, err := st.EnvironConfig()
	if err != nil {
		return nil, errors.Trace(err)
	}
	environ, err := environs.New(config)
	if err != nil {
		return nil, errors.Trace(err)
	}
	netEnviron, ok := environs.SupportsNetworking(environ)
	if !ok {
		return nil, errors.New("environment does not support networking")
	}
	a := newWorkerWithReleaser(st, netEnviron)
	return a, nil
}

func newWorkerWithReleaser(st stateAddresser, releaser releaser) worker.Worker {
	a := &addresserHandler{
		st:       st,
		releaser: releaser,
	}
	w := worker.NewStringsWorker(a)
	return w
}

// Handle is part of the StringsWorker interface.
func (a *addresserHandler) Handle(ids []string) error {
	for _, id := range ids {
		logger.Debugf("received notification about address %v", id)
		addr, err := a.st.IPAddress(id)
		if err != nil {
			if errors.IsNotFound(err) {
				logger.Debugf("address %v was removed; skipping", id)
				continue
			}
			return err
		}
		if addr.Life() != state.Dead {
			logger.Debugf("address %v is not Dead (life %q); skipping", id, addr.Life())
			continue
		}
		err = a.releaseIPAddress(addr)
		if err != nil {
			return err
		}
		logger.Debugf("address %v released", id)
		err = addr.Remove()
		if err != nil {
			return err
		}
		logger.Debugf("address %v removed", id)
	}
	return nil
}

func (a *addresserHandler) releaseIPAddress(addr *state.IPAddress) (err error) {
	defer errors.DeferredAnnotatef(&err, "failed to release address %v", addr.Value())
	var machine *state.Machine
	logger.Debugf("attempting to release dead address %#v", addr.Value())
	machine, err = a.st.Machine(addr.MachineId())
	if err != nil {
		return errors.Annotatef(err, "cannot get allocated machine %q", addr.MachineId())
	}

	var instId instance.Id
	instId, err = machine.InstanceId()
	if err != nil {
		return errors.Annotatef(err, "cannot get machine %q instance ID", addr.MachineId())
	}

	subnetId := network.Id(addr.SubnetId())
	for attempt := common.ShortAttempt.Start(); attempt.Next(); {
		err = a.releaser.ReleaseAddress(instId, subnetId, addr.Address())
		if err == nil {
			return nil
		}
	}
	// Don't remove the address from state so we
	// can retry releasing the address later.
	logger.Warningf("cannot release address %q: %v (will retry)", addr.Value(), err)
	return errors.Trace(err)
}

// SetUp is part of the StringsWorker interface.
func (a *addresserHandler) SetUp() (apiWatcher.StringsWatcher, error) {
	w := a.st.WatchIPAddresses()
	return w, nil
}

// TearDown is part of the StringsWorker interface.
func (a *addresserHandler) TearDown() error {
	return nil
}
<|endoftext|>


<commit_before>package test

import (
	"sync"
	"testing"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

func TestAllHooks(t *testing.T) {
	assert := assert.New(t)

	logger, hook := NewNullLogger()
	assert.Nil(hook.LastEntry())
	assert.Equal(0, len(hook.Entries))

	logger.Error("Hello error")
	assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
	assert.Equal("Hello error", hook.LastEntry().Message)
	assert.Equal(1, len(hook.Entries))

	logger.Warn("Hello warning")
	assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
	assert.Equal("Hello warning", hook.LastEntry().Message)
	assert.Equal(2, len(hook.Entries))

	hook.Reset()
	assert.Nil(hook.LastEntry())
	assert.Equal(0, len(hook.Entries))

	hook = NewGlobal()

	logrus.Error("Hello error")
	assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
	assert.Equal("Hello error", hook.LastEntry().Message)
	assert.Equal(1, len(hook.Entries))
}

func TestLoggingWithHooksRace(t *testing.T) {
	assert := assert.New(t)
	logger, hook := NewNullLogger()

	var wg sync.WaitGroup
	wg.Add(100)

	for i := 0; i < 100; i++ {
		go func() {
			logger.Info("info")
			wg.Done()
		}()
	}

	assert.Equal(logrus.InfoLevel, hook.LastEntry().Level)
	assert.Equal("info", hook.LastEntry().Message)

	wg.Wait()

	entries := hook.AllEntries()
	assert.Equal(100, len(entries))
}
<commit_msg>Fix a race condition in TestLoggingWithHooksRace<commit_after>package test

import (
	"sync"
	"testing"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

func TestAllHooks(t *testing.T) {
	assert := assert.New(t)

	logger, hook := NewNullLogger()
	assert.Nil(hook.LastEntry())
	assert.Equal(0, len(hook.Entries))

	logger.Error("Hello error")
	assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
	assert.Equal("Hello error", hook.LastEntry().Message)
	assert.Equal(1, len(hook.Entries))

	logger.Warn("Hello warning")
	assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
	assert.Equal("Hello warning", hook.LastEntry().Message)
	assert.Equal(2, len(hook.Entries))

	hook.Reset()
	assert.Nil(hook.LastEntry())
	assert.Equal(0, len(hook.Entries))

	hook = NewGlobal()

	logrus.Error("Hello error")
	assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
	assert.Equal("Hello error", hook.LastEntry().Message)
	assert.Equal(1, len(hook.Entries))
}

func TestLoggingWithHooksRace(t *testing.T) {
	assert := assert.New(t)
	logger, hook := NewNullLogger()

	var wg sync.WaitGroup
	wg.Add(100)

	for i := 0; i < 100; i++ {
		go func() {
			logger.Info("info")
			wg.Done()
		}()
	}

	wg.Wait()

	assert.Equal(logrus.InfoLevel, hook.LastEntry().Level)
	assert.Equal("info", hook.LastEntry().Message)

	entries := hook.AllEntries()
	assert.Equal(100, len(entries))
}
<|endoftext|>


<commit_before>package commands

import (
	"os"
	"os/exec"
	"time"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/lfs"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
	"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)

var (
	fetchCmd = &cobra.Command{
		Use:   "fetch",
		Short: "fetch",
		Run:   fetchCommand,
	}
)

func fetchCommand(cmd *cobra.Command, args []string) {
	var ref string
	var err error

	if len(args) == 1 {
		ref = args[0]
	} else {
		ref, err = git.CurrentRef()
		if err != nil {
			Panic(err, "Could not fetch")
		}
	}

	pointers, err := lfs.ScanRefs(ref, "")
	if err != nil {
		Panic(err, "Could not scan for Git LFS files")
	}

	q := lfs.NewDownloadQueue(lfs.Config.ConcurrentTransfers(), len(pointers))

	for _, p := range pointers {
		q.Add(lfs.NewDownloadable(p))
	}

	target, err := git.ResolveRef(ref)
	if err != nil {
		Panic(err, "Could not resolve git ref")
	}

	current, err := git.CurrentRef()
	if err != nil {
		Panic(err, "Could not fetch the current git ref")
	}

	finished := make(chan int)

	if target == current {
		// We just downloaded the files for the current ref, we can copy them into
		// the working directory and update the git index. We're doing this in a
		// goroutine so they can be copied as they come in, for efficiency.
		watch := q.Watch()

		go func() {
			files := make(map[string]*lfs.WrappedPointer, len(pointers))
			for _, pointer := range pointers {
				files[pointer.Oid] = pointer
			}

			// Fire up the update-index command
			cmd := exec.Command("git", "update-index", "-q", "--refresh", "--stdin")
			stdin, err := cmd.StdinPipe()
			if err != nil {
				Panic(err, "Could not update the index")
			}

			if err := cmd.Start(); err != nil {
				Panic(err, "Could not update the index")
			}

			// As files come in, write them to the wd and update the index
			for oid := range watch {
				pointer, ok := files[oid]
				if !ok {
					continue
				}

				file, err := os.Create(pointer.Name)
				if err != nil {
					Panic(err, "Could not create working directory file")
				}

				if err := lfs.PointerSmudge(file, pointer.Pointer, pointer.Name, nil); err != nil {
					Panic(err, "Could not write working directory file")
				}
				file.Close()

				stdin.Write([]byte(pointer.Name + "\n"))
			}

			stdin.Close()
			if err := cmd.Wait(); err != nil {
				Panic(err, "Error updating the git index")
			}
			close(finished)
		}()
	} else {
		close(finished)
	}

	processQueue := time.Now()
	q.Process()
	tracerx.PerformanceSince("process queue", processQueue)
	<-finished
}

func init() {
	RootCmd.AddCommand(fetchCmd)
}
<commit_msg>アーア アアアア アーアー<commit_after>package commands

import (
	"os"
	"os/exec"
	"sync"
	"time"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/lfs"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
	"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)

var (
	fetchCmd = &cobra.Command{
		Use:   "fetch",
		Short: "fetch",
		Run:   fetchCommand,
	}
)

func fetchCommand(cmd *cobra.Command, args []string) {
	var ref string
	var err error

	if len(args) == 1 {
		ref = args[0]
	} else {
		ref, err = git.CurrentRef()
		if err != nil {
			Panic(err, "Could not fetch")
		}
	}

	pointers, err := lfs.ScanRefs(ref, "")
	if err != nil {
		Panic(err, "Could not scan for Git LFS files")
	}

	q := lfs.NewDownloadQueue(lfs.Config.ConcurrentTransfers(), len(pointers))

	for _, p := range pointers {
		q.Add(lfs.NewDownloadable(p))
	}

	target, err := git.ResolveRef(ref)
	if err != nil {
		Panic(err, "Could not resolve git ref")
	}

	current, err := git.CurrentRef()
	if err != nil {
		Panic(err, "Could not fetch the current git ref")
	}

	var wait sync.WaitGroup
	wait.Add(1)

	if target == current {
		// We just downloaded the files for the current ref, we can copy them into
		// the working directory and update the git index. We're doing this in a
		// goroutine so they can be copied as they come in, for efficiency.
		watch := q.Watch()

		go func() {
			files := make(map[string]*lfs.WrappedPointer, len(pointers))
			for _, pointer := range pointers {
				files[pointer.Oid] = pointer
			}

			// Fire up the update-index command
			cmd := exec.Command("git", "update-index", "-q", "--refresh", "--stdin")
			stdin, err := cmd.StdinPipe()
			if err != nil {
				Panic(err, "Could not update the index")
			}

			if err := cmd.Start(); err != nil {
				Panic(err, "Could not update the index")
			}

			// As files come in, write them to the wd and update the index
			for oid := range watch {
				pointer, ok := files[oid]
				if !ok {
					continue
				}

				file, err := os.Create(pointer.Name)
				if err != nil {
					Panic(err, "Could not create working directory file")
				}

				if err := lfs.PointerSmudge(file, pointer.Pointer, pointer.Name, nil); err != nil {
					Panic(err, "Could not write working directory file")
				}
				file.Close()

				stdin.Write([]byte(pointer.Name + "\n"))
			}

			stdin.Close()
			if err := cmd.Wait(); err != nil {
				Panic(err, "Error updating the git index")
			}
			wait.Done()
		}()
	} else {
		wait.Done()
	}

	processQueue := time.Now()
	q.Process()
	tracerx.PerformanceSince("process queue", processQueue)
	wait.Wait()
}

func init() {
	RootCmd.AddCommand(fetchCmd)
}
<|endoftext|>


<commit_before>package gobrake

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
	"time"
)

type QueryInfo struct {
	Method string
	Route  string
	Query  string
	Func   string
	File   string
	Line   int
	Start  time.Time
	End    time.Time
}

type queryKey struct {
	Method string    `json:"method"`
	Route  string    `json:"route"`
	Query  string    `json:"query"`
	Func   string    `json:"function"`
	File   string    `json:"file"`
	Line   int       `json:"line"`
	Time   time.Time `json:"time"`
}

type queryKeyStat struct {
	queryKey
	*routeStat
}

type queryStats struct {
	opt    *NotifierOptions
	apiURL string

	flushTimer *time.Timer
	addWG      *sync.WaitGroup

	mu sync.Mutex
	m  map[queryKey]*routeStat
}

func newQueryStats(opt *NotifierOptions) *queryStats {
	return &queryStats{
		opt: opt,
		apiURL: fmt.Sprintf("%s/api/v5/projects/%d/queries-stats",
			opt.Host, opt.ProjectId),
	}
}

func (s *queryStats) init() {
	if s.flushTimer == nil {
		s.flushTimer = time.AfterFunc(flushPeriod, s.flush)
		s.addWG = new(sync.WaitGroup)
		s.m = make(map[queryKey]*routeStat)
	}
}

func (s *queryStats) flush() {
	s.mu.Lock()

	s.flushTimer = nil
	addWG := s.addWG
	s.addWG = nil
	m := s.m
	s.m = nil

	s.mu.Unlock()

	addWG.Wait()
	err := s.send(m)
	if err != nil {
		logger.Printf("queryStats.send failed: %s", err)
	}
}

type queriesOut struct {
	Env     string         `json:"environment"`
	Queries []queryKeyStat `json:"queries"`
}

func (s *queryStats) send(m map[queryKey]*routeStat) error {
	var queries []queryKeyStat
	for k, v := range m {
		err := v.td.Compress()
		if err != nil {
			return err
		}

		b, err := v.td.AsBytes()
		if err != nil {
			return err
		}
		v.TDigest = b

		queries = append(queries, queryKeyStat{
			queryKey:  k,
			routeStat: v,
		})
	}

	buf := buffers.Get().(*bytes.Buffer)
	defer buffers.Put(buf)
	buf.Reset()

	out := queriesOut{
		Env:     s.opt.Environment,
		Queries: queries,
	}
	err := json.NewEncoder(buf).Encode(&out)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", s.apiURL, buf)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", "Bearer "+s.opt.ProjectKey)
	req.Header.Set("Content-Type", "application/json")
	resp, err := s.opt.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	buf.Reset()
	_, err = buf.ReadFrom(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	switch resp.StatusCode {
	case http.StatusUnauthorized:
		return errUnauthorized
	}

	err = fmt.Errorf("got unexpected response status=%q", resp.Status)
	return err
}

func (s *queryStats) Notify(c context.Context, q *QueryInfo) error {
	key := queryKey{
		Method: q.Method,
		Route:  q.Route,
		Query:  q.Query,
		Func:   q.Func,
		File:   q.File,
		Line:   q.Line,
		Time:   q.Start.UTC().Truncate(time.Minute),
	}

	s.mu.Lock()
	s.init()
	stat, ok := s.m[key]
	if !ok {
		stat = &routeStat{}
		s.m[key] = stat
	}
	addWG := s.addWG
	s.addWG.Add(1)
	s.mu.Unlock()

	dur := q.End.Sub(q.Start)

	stat.mu.Lock()
	err := stat.Add(dur)
	addWG.Done()
	stat.mu.Unlock()

	trace := RouteTraceFromContext(c)
	if trace != nil {
		trace.IncGroup("sql", dur)
	}

	return err
}
<commit_msg>Cleanup<commit_after>package gobrake

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
	"time"
)

type QueryInfo struct {
	Method string
	Route  string
	Query  string
	Func   string
	File   string
	Line   int
	Start  time.Time
	End    time.Time
}

type queryKey struct {
	Method string    `json:"method"`
	Route  string    `json:"route"`
	Query  string    `json:"query"`
	Func   string    `json:"function"`
	File   string    `json:"file"`
	Line   int       `json:"line"`
	Time   time.Time `json:"time"`
}

type queryKeyStat struct {
	queryKey
	*routeStat
}

type queryStats struct {
	opt    *NotifierOptions
	apiURL string

	flushTimer *time.Timer
	addWG      *sync.WaitGroup

	mu sync.Mutex
	m  map[queryKey]*routeStat
}

func newQueryStats(opt *NotifierOptions) *queryStats {
	return &queryStats{
		opt: opt,
		apiURL: fmt.Sprintf("%s/api/v5/projects/%d/queries-stats",
			opt.Host, opt.ProjectId),
	}
}

func (s *queryStats) init() {
	if s.flushTimer == nil {
		s.flushTimer = time.AfterFunc(flushPeriod, s.flush)
		s.addWG = new(sync.WaitGroup)
		s.m = make(map[queryKey]*routeStat)
	}
}

func (s *queryStats) flush() {
	s.mu.Lock()

	s.flushTimer = nil
	addWG := s.addWG
	s.addWG = nil
	m := s.m
	s.m = nil

	s.mu.Unlock()

	addWG.Wait()
	err := s.send(m)
	if err != nil {
		logger.Printf("queryStats.send failed: %s", err)
	}
}

type queriesOut struct {
	Env     string         `json:"environment"`
	Queries []queryKeyStat `json:"queries"`
}

func (s *queryStats) send(m map[queryKey]*routeStat) error {
	var queries []queryKeyStat
	for k, v := range m {
		err := v.td.Compress()
		if err != nil {
			return err
		}

		b, err := v.td.AsBytes()
		if err != nil {
			return err
		}
		v.TDigest = b

		queries = append(queries, queryKeyStat{
			queryKey:  k,
			routeStat: v,
		})
	}

	buf := buffers.Get().(*bytes.Buffer)
	defer buffers.Put(buf)
	buf.Reset()

	out := queriesOut{
		Env:     s.opt.Environment,
		Queries: queries,
	}
	err := json.NewEncoder(buf).Encode(&out)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", s.apiURL, buf)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", "Bearer "+s.opt.ProjectKey)
	req.Header.Set("Content-Type", "application/json")
	resp, err := s.opt.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	buf.Reset()
	_, err = buf.ReadFrom(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	switch resp.StatusCode {
	case http.StatusUnauthorized:
		return errUnauthorized
	}

	err = fmt.Errorf("got unexpected response status=%q", resp.Status)
	return err
}

func (s *queryStats) Notify(c context.Context, q *QueryInfo) error {
	key := queryKey{
		Method: q.Method,
		Route:  q.Route,
		Query:  q.Query,
		Func:   q.Func,
		File:   q.File,
		Line:   q.Line,
		Time:   q.Start.UTC().Truncate(time.Minute),
	}

	s.mu.Lock()
	s.init()
	stat, ok := s.m[key]
	if !ok {
		stat = &routeStat{}
		s.m[key] = stat
	}
	addWG := s.addWG
	s.addWG.Add(1)
	s.mu.Unlock()

	dur := q.End.Sub(q.Start)

	stat.mu.Lock()
	err := stat.Add(dur)
	addWG.Done()
	stat.mu.Unlock()

	return err
}
<|endoftext|>


<commit_before>package securitygroup

import (
	"strings"

	"github.com/cloudfoundry/cli/cf/api/organizations"
	"github.com/cloudfoundry/cli/cf/api/security_groups"
	sgbinder "github.com/cloudfoundry/cli/cf/api/security_groups/spaces"
	"github.com/cloudfoundry/cli/cf/api/spaces"
	"github.com/cloudfoundry/cli/cf/command_metadata"
	"github.com/cloudfoundry/cli/cf/configuration/core_config"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/requirements"
	"github.com/cloudfoundry/cli/cf/terminal"
	"github.com/codegangsta/cli"
)

type BindSecurityGroup struct {
	ui                terminal.UI
	configRepo        core_config.Reader
	orgRepo           organizations.OrganizationRepository
	spaceRepo         spaces.SpaceRepository
	securityGroupRepo security_groups.SecurityGroupRepo
	spaceBinder       sgbinder.SecurityGroupSpaceBinder
}

func NewBindSecurityGroup(
	ui terminal.UI,
	configRepo core_config.Reader,
	securityGroupRepo security_groups.SecurityGroupRepo,
	spaceRepo spaces.SpaceRepository,
	orgRepo organizations.OrganizationRepository,
	spaceBinder sgbinder.SecurityGroupSpaceBinder,
) BindSecurityGroup {
	return BindSecurityGroup{
		ui:                ui,
		configRepo:        configRepo,
		spaceRepo:         spaceRepo,
		orgRepo:           orgRepo,
		securityGroupRepo: securityGroupRepo,
		spaceBinder:       spaceBinder,
	}
}

func (cmd BindSecurityGroup) Metadata() command_metadata.CommandMetadata {
	primaryUsage := T("CF_NAME bind-security-group SECURITY_GROUP ORG SPACE")
	tipUsage := T("TIP: Changes will not apply to existing running applications until they are restarted.")
	return command_metadata.CommandMetadata{
		Name:        "bind-security-group",
		Description: T("Bind a security group to a space"),
		Usage:       strings.Join([]string{primaryUsage, tipUsage}, "\n\n"),
	}
}

func (cmd BindSecurityGroup) GetRequirements(requirementsFactory requirements.Factory, context *cli.Context) (reqs []requirements.Requirement, err error) {
	if len(context.Args()) != 3 {
		cmd.ui.FailWithUsage(context)
	}

	reqs = append(reqs, requirementsFactory.NewLoginRequirement())
	return
}

func (cmd BindSecurityGroup) Run(context *cli.Context) {
	securityGroupName := context.Args()[0]
	orgName := context.Args()[1]
	spaceName := context.Args()[2]

	cmd.ui.Say(T("Assigning security group {{.security_group}} to space {{.space}} in org {{.organization}} as {{.username}}...",
		map[string]interface{}{
			"security_group": securityGroupName,
			"space":          orgName,
			"organization":   spaceName,
			"username":       cmd.configRepo.Username(),
		}))

	securityGroup, err := cmd.securityGroupRepo.Read(securityGroupName)

	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	org, err := cmd.orgRepo.FindByName(orgName)

	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	space, err := cmd.spaceRepo.FindByNameInOrg(spaceName, org.Guid)

	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	err = cmd.spaceBinder.BindSpace(securityGroup.Guid, space.Guid)
	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	cmd.ui.Ok()
	cmd.ui.Say("\n\n")
	cmd.ui.Say(T("TIP: Changes will not apply to existing running applications until they are restarted."))
}
<commit_msg>Correcting status message<commit_after>package securitygroup

import (
	"strings"

	"github.com/cloudfoundry/cli/cf/api/organizations"
	"github.com/cloudfoundry/cli/cf/api/security_groups"
	sgbinder "github.com/cloudfoundry/cli/cf/api/security_groups/spaces"
	"github.com/cloudfoundry/cli/cf/api/spaces"
	"github.com/cloudfoundry/cli/cf/command_metadata"
	"github.com/cloudfoundry/cli/cf/configuration/core_config"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/requirements"
	"github.com/cloudfoundry/cli/cf/terminal"
	"github.com/codegangsta/cli"
)

type BindSecurityGroup struct {
	ui                terminal.UI
	configRepo        core_config.Reader
	orgRepo           organizations.OrganizationRepository
	spaceRepo         spaces.SpaceRepository
	securityGroupRepo security_groups.SecurityGroupRepo
	spaceBinder       sgbinder.SecurityGroupSpaceBinder
}

func NewBindSecurityGroup(
	ui terminal.UI,
	configRepo core_config.Reader,
	securityGroupRepo security_groups.SecurityGroupRepo,
	spaceRepo spaces.SpaceRepository,
	orgRepo organizations.OrganizationRepository,
	spaceBinder sgbinder.SecurityGroupSpaceBinder,
) BindSecurityGroup {
	return BindSecurityGroup{
		ui:                ui,
		configRepo:        configRepo,
		spaceRepo:         spaceRepo,
		orgRepo:           orgRepo,
		securityGroupRepo: securityGroupRepo,
		spaceBinder:       spaceBinder,
	}
}

func (cmd BindSecurityGroup) Metadata() command_metadata.CommandMetadata {
	primaryUsage := T("CF_NAME bind-security-group SECURITY_GROUP ORG SPACE")
	tipUsage := T("TIP: Changes will not apply to existing running applications until they are restarted.")
	return command_metadata.CommandMetadata{
		Name:        "bind-security-group",
		Description: T("Bind a security group to a space"),
		Usage:       strings.Join([]string{primaryUsage, tipUsage}, "\n\n"),
	}
}

func (cmd BindSecurityGroup) GetRequirements(requirementsFactory requirements.Factory, context *cli.Context) (reqs []requirements.Requirement, err error) {
	if len(context.Args()) != 3 {
		cmd.ui.FailWithUsage(context)
	}

	reqs = append(reqs, requirementsFactory.NewLoginRequirement())
	return
}

func (cmd BindSecurityGroup) Run(context *cli.Context) {
	securityGroupName := context.Args()[0]
	orgName := context.Args()[1]
	spaceName := context.Args()[2]

	cmd.ui.Say(T("Assigning security group {{.security_group}} to space {{.space}} in org {{.organization}} as {{.username}}...",
		map[string]interface{}{
			"security_group": securityGroupName,
			"space":          spaceName,
			"organization":   orgName,
			"username":       cmd.configRepo.Username(),
		}))

	securityGroup, err := cmd.securityGroupRepo.Read(securityGroupName)

	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	org, err := cmd.orgRepo.FindByName(orgName)

	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	space, err := cmd.spaceRepo.FindByNameInOrg(spaceName, org.Guid)

	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	err = cmd.spaceBinder.BindSpace(securityGroup.Guid, space.Guid)
	if err != nil {
		cmd.ui.Failed(err.Error())
	}

	cmd.ui.Ok()
	cmd.ui.Say("\n\n")
	cmd.ui.Say(T("TIP: Changes will not apply to existing running applications until they are restarted."))
}
<|endoftext|>


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

package gce

import (
	"fmt"
	"strings"
	"sync"

	"github.com/juju/errors"
	"github.com/juju/utils"
	"github.com/juju/utils/set"

	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/provider/gce/google"
	"github.com/juju/juju/storage"
)

const (
	storageProviderType = storage.ProviderType("gce")
)

func init() {
	//TODO(perrito666) Add explicit pools.
}

type storageProvider struct{}

var _ storage.Provider = (*storageProvider)(nil)

func (g *storageProvider) ValidateConfig(cfg *storage.Config) error {
	return nil
}

func (g *storageProvider) Supports(k storage.StorageKind) bool {
	return k == storage.StorageKindBlock
}

func (g *storageProvider) Scope() storage.Scope {
	return storage.ScopeEnviron
}

func (g *storageProvider) Dynamic() bool {
	return true
}

func (g *storageProvider) FilesystemSource(environConfig *config.Config, providerConfig *storage.Config) (storage.FilesystemSource, error) {
	return nil, errors.NotSupportedf("filesystems")
}

type volumeSource struct {
	gce     gceConnection
	envName string // non-unique, informational only
	envUUID string
}

func (g *storageProvider) VolumeSource(environConfig *config.Config, cfg *storage.Config) (storage.VolumeSource, error) {
	uuid, ok := environConfig.UUID()
	if !ok {
		return nil, errors.NotFoundf("environment UUID")
	}

	// Connect and authenticate.
	env, err := newEnviron(environConfig)
	if err != nil {
		return nil, errors.Annotate(err, "cannot create an environ with this config")
	}

	source := &volumeSource{
		gce:     env.gce,
		envName: environConfig.Name(),
		envUUID: uuid,
	}
	return source, nil
}

type instanceCache map[string]google.Instance

func (c instanceCache) update(gceClient gceConnection, ids ...string) error {
	if len(ids) == 1 {
		if _, ok := c[ids[0]]; ok {
			return nil
		}
	}
	idMap := make(map[string]int, len(ids))
	for _, id := range ids {
		idMap[id] = 0
	}
	instances, err := gceClient.Instances("", google.StatusRunning)
	if err != nil {
		return errors.Annotate(err, "querying instance details")
	}
	for _, instance := range instances {
		if _, ok := idMap[instance.ID]; !ok {
			continue
		}
		c[instance.ID] = instance
	}
	return nil
}

func (c instanceCache) get(id string) (google.Instance, error) {
	inst, ok := c[id]
	if !ok {
		return google.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id)
	}
	return inst, nil
}

func (v *volumeSource) CreateVolumes(params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
	results := make([]storage.CreateVolumesResult, len(params))
	instanceIds := set.NewStrings()
	for i, p := range params {
		if err := v.ValidateVolumeParams(p); err != nil {
			results[i].Error = err
			continue
		}
		instanceIds.Add(string(p.Attachment.InstanceId))
	}

	instances := make(instanceCache)
	if instanceIds.Size() > 1 {
		if err := instances.update(v.gce, instanceIds.Values()...); err != nil {
			logger.Debugf("querying running instances: %v", err)
			// We ignore the error, because we don't want an invalid
			// InstanceId reference from one VolumeParams to prevent
			// the creation of another volume.
		}
	}

	for i, p := range params {
		if results[i].Error != nil {
			continue
		}
		volume, attachment, err := v.createOneVolume(p, instances)
		if err != nil {
			results[i].Error = err
			logger.Errorf("could not create one volume (or attach it): %v", err)
			continue
		}
		results[i].Volume = volume
		results[i].VolumeAttachment = attachment
	}
	return results, nil
}

// mibToGib converts mebibytes to gibibytes.
// GCE expects GiB, we work in MiB; round up
// to nearest GiB.
func mibToGib(m uint64) uint64 {
	return (m + 1023) / 1024
}

func nameVolume(zone string) (string, error) {
	volumeUUID, err := utils.NewUUID()
	if err != nil {
		return "", errors.Annotate(err, "cannot generate uuid to name the volume")
	}
	// type-zone-uuid
	volumeName := fmt.Sprintf("%s--%s", zone, volumeUUID.String())
	return volumeName, nil
}

func (v *volumeSource) createOneVolume(p storage.VolumeParams, instances instanceCache) (volume *storage.Volume, volumeAttachment *storage.VolumeAttachment, err error) {
	var volumeName, zone string
	defer func() {
		if err == nil || volumeName == "" {
			return
		}
		if err := v.gce.RemoveDisk(zone, volumeName); err != nil {
			logger.Warningf("error cleaning up volume %v: %v", volumeName, err)
		}
	}()

	instId := string(p.Attachment.InstanceId)
	if err := instances.update(v.gce, instId); err != nil {
		return nil, nil, errors.Annotatef(err, "cannot add %q to instance cache", instId)
	}
	inst, err := instances.get(instId)
	if err != nil {
		// Can't create the volume without the instance,
		// because we need to know what its AZ is.
		return nil, nil, errors.Annotatef(err, "cannot obtain %q from instance cache", instId)
	}
	persistentType, ok := p.Attributes["type"].(google.DiskType)
	if !ok {
		persistentType = google.DiskPersistentStandard
	}

	zone = inst.ZoneName
	volumeName, err = nameVolume(zone)
	if err != nil {
		return nil, nil, errors.Annotate(err, "cannot create a new volume name")
	}
	// TODO(perrito666) the volumeName is arbitrary and it was crafted this
	// way to help solve the need to have zone all over the place.
	disk := google.DiskSpec{
		SizeHintGB:         mibToGib(p.Size),
		Name:               volumeName,
		PersistentDiskType: persistentType,
		Description:        v.envUUID,
	}

	gceDisks, err := v.gce.CreateDisks(zone, []google.DiskSpec{disk})
	if err != nil {
		return nil, nil, errors.Annotate(err, "cannot create disk")
	}
	if len(gceDisks) != 1 {
		return nil, nil, errors.New(fmt.Sprintf("unexpected number of disks created: %d", len(gceDisks)))
	}
	gceDisk := gceDisks[0]
	// TODO(perrito666) Tag, there are no tags in gce, how do we fix it?

	attachedDisk, err := v.attachOneVolume(gceDisk.Name, google.ModeRW, inst.ID)
	if err != nil {
		return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId)
	}

	volume = &storage.Volume{
		p.Tag,
		storage.VolumeInfo{
			VolumeId:   gceDisk.Name,
			Size:       gceDisk.Size,
			Persistent: true,
		},
	}

	volumeAttachment = &storage.VolumeAttachment{
		p.Tag,
		p.Attachment.Machine,
		storage.VolumeAttachmentInfo{
			DeviceLink: fmt.Sprintf(
				"/dev/disk/by-id/google-%s",
				attachedDisk.DeviceName,
			),
		},
	}

	return volume, volumeAttachment, nil
}

func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) {
	var wg sync.WaitGroup
	wg.Add(len(volNames))
	results := make([]error, len(volNames))
	for i, volumeName := range volNames {
		go func(i int, volumeName string) {
			defer wg.Done()
			results[i] = v.destroyOneVolume(volumeName)
		}(i, volumeName)
	}
	wg.Wait()
	return results, nil
}

func parseVolumeId(volName string) (string, string, error) {
	idRest := strings.SplitN(volName, "--", 2)
	if len(idRest) != 2 {
		return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName))
	}
	zone := idRest[0]
	volumeUUID := idRest[1]
	return zone, volumeUUID, nil

}

func isValidVolume(volumeName string) bool {
	_, _, err := parseVolumeId(volumeName)
	return err == nil
}

func (v *volumeSource) destroyOneVolume(volName string) error {
	zone, _, err := parseVolumeId(volName)
	if err != nil {
		return errors.Annotatef(err, "invalid volume id %q", volName)
	}
	if err := v.gce.RemoveDisk(zone, volName); err != nil {
		return errors.Annotatef(err, "cannot destroy volume %q", volName)
	}
	return nil
}

func (v *volumeSource) ListVolumes() ([]string, error) {
	azs, err := v.gce.AvailabilityZones("")
	if err != nil {
		return nil, errors.Annotate(err, "cannot determine availability zones")
	}
	var volumes []string
	for _, zone := range azs {
		disks, err := v.gce.Disks(zone.Name())
		if err != nil {
			// maybe use available and status also.
			logger.Errorf("cannot get disks for %q zone", zone.Name())
			continue
		}
		for _, disk := range disks {
			if disk.Description != v.envUUID && disk.Description != "" {
				continue
			}
			// We don't want to lay hands on disks we did not create.
			if isValidVolume(disk.Name) {
				volumes = append(volumes, disk.Name)
			}
		}
	}
	return volumes, nil
}
func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) {
	results := make([]storage.DescribeVolumesResult, len(volNames))
	for i, vol := range volNames {
		res, err := v.describeOneVolume(vol)
		if err != nil {
			return nil, errors.Annotate(err, "cannot describe volumes")
		}
		results[i] = res
	}
	return results, nil
}

func (v *volumeSource) describeOneVolume(volName string) (storage.DescribeVolumesResult, error) {
	zone, _, err := parseVolumeId(volName)
	if err != nil {
		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName)
	}
	disk, err := v.gce.Disk(zone, volName)
	if err != nil {
		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot get volume %q", volName)
	}
	desc := storage.DescribeVolumesResult{
		&storage.VolumeInfo{
			Size:     disk.Size,
			VolumeId: disk.Name,
		},
		nil,
	}
	return desc, nil
}

// TODO(perrito666) These rules are yet to be defined.
func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
	return nil
}

func (v *volumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
	results := make([]storage.AttachVolumesResult, len(attachParams))
	for i, attachment := range attachParams {
		volumeName := attachment.VolumeId
		mode := google.ModeRW
		if attachment.ReadOnly {
			mode = google.ModeRW
		}
		instanceId := attachment.InstanceId
		attached, err := v.attachOneVolume(volumeName, mode, string(instanceId))
		if err != nil {
			logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err)
			results[i].Error = err
			continue
		}
		results[i].VolumeAttachment = &storage.VolumeAttachment{
			attachment.Volume,
			attachment.Machine,
			storage.VolumeAttachmentInfo{
				DeviceName: attached.DeviceName,
			},
		}
	}
	return results, nil
}

func (v *volumeSource) attachOneVolume(volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) {
	zone, _, err := parseVolumeId(volumeName)
	if err != nil {
		return nil, errors.Annotate(err, "invalid volume name")
	}
	instanceDisks, err := v.gce.InstanceDisks(zone, instanceId)
	if err != nil {
		return nil, errors.Annotate(err, "cannot verify if the disk is already in the instance")
	}
	// Is it already attached?
	for _, disk := range instanceDisks {
		if disk.VolumeName == volumeName {
			return disk, nil
		}
	}

	attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode)
	if err != nil {
		return nil, errors.Annotate(err, "cannot attach volume")
	}
	return attachment, nil
}

func (v *volumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) {
	result := make([]error, len(attachParams))
	for i, volumeAttachment := range attachParams {
		result[i] = v.detachOneVolume(volumeAttachment)
	}
	return result, nil
}

func (v *volumeSource) detachOneVolume(attachParam storage.VolumeAttachmentParams) error {
	instId := attachParam.InstanceId
	volumeName := attachParam.VolumeId
	zone, _, err := parseVolumeId(volumeName)
	if err != nil {
		return errors.Annotatef(err, "%q is not a valid volume id", volumeName)
	}
	return v.gce.DetachDisk(zone, string(instId), volumeName)
}
<commit_msg>Addressed @natefinch comments<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package gce

import (
	"fmt"
	"strings"
	"sync"

	"github.com/juju/errors"
	"github.com/juju/utils"
	"github.com/juju/utils/set"

	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/provider/gce/google"
	"github.com/juju/juju/storage"
)

const (
	storageProviderType = storage.ProviderType("gce")
)

func init() {
	//TODO(perrito666) Add explicit pools.
}

type storageProvider struct{}

var _ storage.Provider = (*storageProvider)(nil)

func (g *storageProvider) ValidateConfig(cfg *storage.Config) error {
	return nil
}

func (g *storageProvider) Supports(k storage.StorageKind) bool {
	return k == storage.StorageKindBlock
}

func (g *storageProvider) Scope() storage.Scope {
	return storage.ScopeEnviron
}

func (g *storageProvider) Dynamic() bool {
	return true
}

func (g *storageProvider) FilesystemSource(environConfig *config.Config, providerConfig *storage.Config) (storage.FilesystemSource, error) {
	return nil, errors.NotSupportedf("filesystems")
}

type volumeSource struct {
	gce     gceConnection
	envName string // non-unique, informational only
	envUUID string
}

func (g *storageProvider) VolumeSource(environConfig *config.Config, cfg *storage.Config) (storage.VolumeSource, error) {
	uuid, ok := environConfig.UUID()
	if !ok {
		return nil, errors.NotFoundf("environment UUID")
	}

	// Connect and authenticate.
	env, err := newEnviron(environConfig)
	if err != nil {
		return nil, errors.Annotate(err, "cannot create an environ with this config")
	}

	source := &volumeSource{
		gce:     env.gce,
		envName: environConfig.Name(),
		envUUID: uuid,
	}
	return source, nil
}

type instanceCache map[string]google.Instance

func (c instanceCache) update(gceClient gceConnection, ids ...string) error {
	if len(ids) == 1 {
		if _, ok := c[ids[0]]; ok {
			return nil
		}
	}
	idMap := make(map[string]int, len(ids))
	for _, id := range ids {
		idMap[id] = 0
	}
	instances, err := gceClient.Instances("", google.StatusRunning)
	if err != nil {
		return errors.Annotate(err, "querying instance details")
	}
	for _, instance := range instances {
		if _, ok := idMap[instance.ID]; !ok {
			continue
		}
		c[instance.ID] = instance
	}
	return nil
}

func (c instanceCache) get(id string) (google.Instance, error) {
	inst, ok := c[id]
	if !ok {
		return google.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id)
	}
	return inst, nil
}

func (v *volumeSource) CreateVolumes(params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
	results := make([]storage.CreateVolumesResult, len(params))
	instanceIds := set.NewStrings()
	for i, p := range params {
		if err := v.ValidateVolumeParams(p); err != nil {
			results[i].Error = err
			continue
		}
		instanceIds.Add(string(p.Attachment.InstanceId))
	}

	instances := make(instanceCache)
	if instanceIds.Size() > 1 {
		if err := instances.update(v.gce, instanceIds.Values()...); err != nil {
			logger.Debugf("querying running instances: %v", err)
			// We ignore the error, because we don't want an invalid
			// InstanceId reference from one VolumeParams to prevent
			// the creation of another volume.
		}
	}

	for i, p := range params {
		if results[i].Error != nil {
			continue
		}
		volume, attachment, err := v.createOneVolume(p, instances)
		if err != nil {
			results[i].Error = err
			logger.Errorf("could not create one volume (or attach it): %v", err)
			continue
		}
		results[i].Volume = volume
		results[i].VolumeAttachment = attachment
	}
	return results, nil
}

// mibToGib converts mebibytes to gibibytes.
// GCE expects GiB, we work in MiB; round up
// to nearest GiB.
func mibToGib(m uint64) uint64 {
	return (m + 1023) / 1024
}

func nameVolume(zone string) (string, error) {
	volumeUUID, err := utils.NewUUID()
	if err != nil {
		return "", errors.Annotate(err, "cannot generate uuid to name the volume")
	}
	// type-zone-uuid
	volumeName := fmt.Sprintf("%s--%s", zone, volumeUUID.String())
	return volumeName, nil
}

func (v *volumeSource) createOneVolume(p storage.VolumeParams, instances instanceCache) (volume *storage.Volume, volumeAttachment *storage.VolumeAttachment, err error) {
	var volumeName, zone string
	defer func() {
		if err == nil || volumeName == "" {
			return
		}
		if err := v.gce.RemoveDisk(zone, volumeName); err != nil {
			logger.Warningf("error cleaning up volume %v: %v", volumeName, err)
		}
	}()

	instId := string(p.Attachment.InstanceId)
	if err := instances.update(v.gce, instId); err != nil {
		return nil, nil, errors.Annotatef(err, "cannot add %q to instance cache", instId)
	}
	inst, err := instances.get(instId)
	if err != nil {
		// Can't create the volume without the instance,
		// because we need to know what its AZ is.
		return nil, nil, errors.Annotatef(err, "cannot obtain %q from instance cache", instId)
	}
	persistentType, ok := p.Attributes["type"].(google.DiskType)
	if !ok {
		persistentType = google.DiskPersistentStandard
	}

	zone = inst.ZoneName
	volumeName, err = nameVolume(zone)
	if err != nil {
		return nil, nil, errors.Annotate(err, "cannot create a new volume name")
	}
	// TODO(perrito666) the volumeName is arbitrary and it was crafted this
	// way to help solve the need to have zone all over the place.
	disk := google.DiskSpec{
		SizeHintGB:         mibToGib(p.Size),
		Name:               volumeName,
		PersistentDiskType: persistentType,
		Description:        v.envUUID,
	}

	gceDisks, err := v.gce.CreateDisks(zone, []google.DiskSpec{disk})
	if err != nil {
		return nil, nil, errors.Annotate(err, "cannot create disk")
	}
	if len(gceDisks) != 1 {
		return nil, nil, errors.New(fmt.Sprintf("unexpected number of disks created: %d", len(gceDisks)))
	}
	gceDisk := gceDisks[0]
	// TODO(perrito666) Tag, there are no tags in gce, how do we fix it?

	attachedDisk, err := v.attachOneVolume(gceDisk.Name, google.ModeRW, inst.ID)
	if err != nil {
		return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId)
	}

	volume = &storage.Volume{
		p.Tag,
		storage.VolumeInfo{
			VolumeId:   gceDisk.Name,
			Size:       gceDisk.Size,
			Persistent: true,
		},
	}

	volumeAttachment = &storage.VolumeAttachment{
		p.Tag,
		p.Attachment.Machine,
		storage.VolumeAttachmentInfo{
			DeviceLink: fmt.Sprintf(
				"/dev/disk/by-id/google-%s",
				attachedDisk.DeviceName,
			),
		},
	}

	return volume, volumeAttachment, nil
}

func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) {
	var wg sync.WaitGroup
	wg.Add(len(volNames))
	results := make([]error, len(volNames))
	for i, volumeName := range volNames {
		go func(i int, volumeName string) {
			defer wg.Done()
			results[i] = v.destroyOneVolume(volumeName)
		}(i, volumeName)
	}
	wg.Wait()
	return results, nil
}

func parseVolumeId(volName string) (string, string, error) {
	idRest := strings.SplitN(volName, "--", 2)
	if len(idRest) != 2 {
		return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName))
	}
	zone := idRest[0]
	volumeUUID := idRest[1]
	return zone, volumeUUID, nil

}

func isValidVolume(volumeName string) bool {
	_, _, err := parseVolumeId(volumeName)
	return err == nil
}

func (v *volumeSource) destroyOneVolume(volName string) error {
	zone, _, err := parseVolumeId(volName)
	if err != nil {
		return errors.Annotatef(err, "invalid volume id %q", volName)
	}
	if err := v.gce.RemoveDisk(zone, volName); err != nil {
		return errors.Annotatef(err, "cannot destroy volume %q", volName)
	}
	return nil
}

func (v *volumeSource) ListVolumes() ([]string, error) {
	azs, err := v.gce.AvailabilityZones("")
	if err != nil {
		return nil, errors.Annotate(err, "cannot determine availability zones")
	}
	var volumes []string
	for _, zone := range azs {
		disks, err := v.gce.Disks(zone.Name())
		if err != nil {
			// maybe use available and status also.
			logger.Errorf("cannot get disks for %q zone", zone.Name())
			continue
		}
		for _, disk := range disks {
			// Blank disk description means an older disk or a disk
			// not created by storage, we should not touch it.
			if disk.Description != v.envUUID && disk.Description != "" {
				continue
			}
			// We don't want to lay hands on disks we did not create.
			if isValidVolume(disk.Name) {
				volumes = append(volumes, disk.Name)
			}
		}
	}
	return volumes, nil
}
func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) {
	results := make([]storage.DescribeVolumesResult, len(volNames))
	for i, vol := range volNames {
		res, err := v.describeOneVolume(vol)
		if err != nil {
			return nil, errors.Annotate(err, "cannot describe volumes")
		}
		results[i] = res
	}
	return results, nil
}

func (v *volumeSource) describeOneVolume(volName string) (storage.DescribeVolumesResult, error) {
	zone, _, err := parseVolumeId(volName)
	if err != nil {
		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName)
	}
	disk, err := v.gce.Disk(zone, volName)
	if err != nil {
		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot get volume %q", volName)
	}
	desc := storage.DescribeVolumesResult{
		&storage.VolumeInfo{
			Size:     disk.Size,
			VolumeId: disk.Name,
		},
		nil,
	}
	return desc, nil
}

// TODO(perrito666) These rules are yet to be defined.
func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
	return nil
}

func (v *volumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
	results := make([]storage.AttachVolumesResult, len(attachParams))
	for i, attachment := range attachParams {
		volumeName := attachment.VolumeId
		mode := google.ModeRW
		if attachment.ReadOnly {
			mode = google.ModeRW
		}
		instanceId := attachment.InstanceId
		attached, err := v.attachOneVolume(volumeName, mode, string(instanceId))
		if err != nil {
			logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err)
			results[i].Error = err
			continue
		}
		results[i].VolumeAttachment = &storage.VolumeAttachment{
			attachment.Volume,
			attachment.Machine,
			storage.VolumeAttachmentInfo{
				DeviceName: attached.DeviceName,
			},
		}
	}
	return results, nil
}

func (v *volumeSource) attachOneVolume(volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) {
	zone, _, err := parseVolumeId(volumeName)
	if err != nil {
		return nil, errors.Annotate(err, "invalid volume name")
	}
	instanceDisks, err := v.gce.InstanceDisks(zone, instanceId)
	if err != nil {
		return nil, errors.Annotate(err, "cannot verify if the disk is already in the instance")
	}
	// Is it already attached?
	for _, disk := range instanceDisks {
		if disk.VolumeName == volumeName {
			return disk, nil
		}
	}

	attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode)
	if err != nil {
		return nil, errors.Annotate(err, "cannot attach volume")
	}
	return attachment, nil
}

func (v *volumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) {
	result := make([]error, len(attachParams))
	for i, volumeAttachment := range attachParams {
		result[i] = v.detachOneVolume(volumeAttachment)
	}
	return result, nil
}

func (v *volumeSource) detachOneVolume(attachParam storage.VolumeAttachmentParams) error {
	instId := attachParam.InstanceId
	volumeName := attachParam.VolumeId
	zone, _, err := parseVolumeId(volumeName)
	if err != nil {
		return errors.Annotatef(err, "%q is not a valid volume id", volumeName)
	}
	return v.gce.DetachDisk(zone, string(instId), volumeName)
}
<|endoftext|>


<commit_before>package cfutil

import (
	"errors"
	"fmt"
	consul "github.com/hashicorp/consul/api"
	"net/url"
	"os"
)

// Services() returns the list of services available from the
// Consul cluster
func Services() ([]string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return nil, err
	}
	catalogServices, _, err := client.Catalog().Services(nil)
	if err != nil {
		return []string{}, err
	}
	services := make([]string, len(catalogServices))
	for k := range catalogServices {
		services = append(services, k)
	}
	return services, nil
}

func DiscoverServiceURL(serviceName, tags string) (string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	services, _, err := client.Catalog().Service(serviceName, tags, nil)
	if err != nil {
		return "", fmt.Errorf("Service `%s` not found: %s", serviceName, err)
	}
	if len(services) > 0 {
		return CreateURLFromServiceCatalog(services[0])
	}
	return "", fmt.Errorf("Service `%s` not found", serviceName)

}

func CreateURLFromServiceCatalog(catalog *consul.CatalogService) (string, error) {
	var serviceURL url.URL
	if catalog.ServicePort == 443 {
		serviceURL.Scheme = "https"
		serviceURL.Host = catalog.ServiceAddress
	} else {
		serviceURL.Scheme = "http"
		serviceURL.Host = fmt.Sprintf("%s:%d", catalog.ServiceAddress, catalog.ServicePort)
	}
	return serviceURL.String(), nil
}

// Use ServiceRegister() to register your app in the Consul cluster
// Optionally you can provide a health endpoint on your URL and
// a number of tags to make your service more discoverable
func ServiceRegister(name string, path string, tags ...string) error {
	appEnv, _ := Current()
	client, err := NewConsulClient()
	if err != nil {
		return err
	}
	schema, port := schemaAndPortForServices()
	err = client.Agent().ServiceRegister(&consul.AgentServiceRegistration{
		Name:    name,
		Address: appEnv.ApplicationURIs[0],
		Port:    port,
		Tags:    tags,
		Check: &consul.AgentServiceCheck{
			HTTP:     fmt.Sprintf(schema + "://" + appEnv.ApplicationURIs[0] + path),
			Interval: "60s",
		},
	})
	if err != nil {
		return err
	}
	return nil
}

// NewConsulClient() returns a new consul client which you can use to
// access the Consul cluster HTTP API. It uses `CONSUL_MASTER` and
// `CONSUL_TOKEN` environment variables to set up the HTTP API connection.
func NewConsulClient() (*consul.Client, error) {
	dialScheme, dialHost, err := consulDialstring("consul")
	if err != nil {
		return nil, err
	}
	client, consulErr := consul.NewClient(&consul.Config{
		Address: dialHost,
		Scheme:  dialScheme,
		Token:   os.Getenv("CONSUL_TOKEN"),
	})
	if consulErr != nil {
		return nil, consulErr
	}
	return client, nil
}

func GetConsulKey(mooncoreKey string) (string, error) {
	ns := ConsulNamespace()
	key := "mooncore/" + ns + "/" + mooncoreKey
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	kvPair, _, err := client.KV().Get(key, nil)
	if err != nil {
		return "", err
	}
	if kvPair == nil || kvPair.Value == nil {
		return "", fmt.Errorf("Key not found")
	}
	return string(kvPair.Value), nil
}

func ConsulNamespace() string {
	return os.Getenv("CONSUL_NAMESPACE")
}

func consulDialstring(serviceName string) (string, string, error) {
	consulMaster := ""
	if consulMaster = os.Getenv("CONSUL_MASTER"); consulMaster != "" {
		parsed, err := url.Parse(consulMaster)
		if err == nil {
			return parsed.Scheme, parsed.Host, nil
		}
	}
	return "", "", errors.New("CONSUL_MASTER not found or invalid url")
}

func schemaAndPortForServices() (string, int) {
	if ForceHTTP() {
		return "http", 80
	}
	return "https", 443
}
<commit_msg>Fix declaration<commit_after>package cfutil

import (
	"errors"
	"fmt"
	consul "github.com/hashicorp/consul/api"
	"net/url"
	"os"
)

// Services() returns the list of services available from the
// Consul cluster
func Services() ([]string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return nil, err
	}
	catalogServices, _, err := client.Catalog().Services(nil)
	if err != nil {
		return []string{}, err
	}
	var services []string
	for k := range catalogServices {
		services = append(services, k)
	}
	return services, nil
}

func DiscoverServiceURL(serviceName, tags string) (string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	services, _, err := client.Catalog().Service(serviceName, tags, nil)
	if err != nil {
		return "", fmt.Errorf("Service `%s` not found: %s", serviceName, err)
	}
	if len(services) > 0 {
		return CreateURLFromServiceCatalog(services[0])
	}
	return "", fmt.Errorf("Service `%s` not found", serviceName)

}

func CreateURLFromServiceCatalog(catalog *consul.CatalogService) (string, error) {
	var serviceURL url.URL
	if catalog.ServicePort == 443 {
		serviceURL.Scheme = "https"
		serviceURL.Host = catalog.ServiceAddress
	} else {
		serviceURL.Scheme = "http"
		serviceURL.Host = fmt.Sprintf("%s:%d", catalog.ServiceAddress, catalog.ServicePort)
	}
	return serviceURL.String(), nil
}

// Use ServiceRegister() to register your app in the Consul cluster
// Optionally you can provide a health endpoint on your URL and
// a number of tags to make your service more discoverable
func ServiceRegister(name string, path string, tags ...string) error {
	appEnv, _ := Current()
	client, err := NewConsulClient()
	if err != nil {
		return err
	}
	schema, port := schemaAndPortForServices()
	err = client.Agent().ServiceRegister(&consul.AgentServiceRegistration{
		Name:    name,
		Address: appEnv.ApplicationURIs[0],
		Port:    port,
		Tags:    tags,
		Check: &consul.AgentServiceCheck{
			HTTP:     fmt.Sprintf(schema + "://" + appEnv.ApplicationURIs[0] + path),
			Interval: "60s",
		},
	})
	if err != nil {
		return err
	}
	return nil
}

// NewConsulClient() returns a new consul client which you can use to
// access the Consul cluster HTTP API. It uses `CONSUL_MASTER` and
// `CONSUL_TOKEN` environment variables to set up the HTTP API connection.
func NewConsulClient() (*consul.Client, error) {
	dialScheme, dialHost, err := consulDialstring("consul")
	if err != nil {
		return nil, err
	}
	client, consulErr := consul.NewClient(&consul.Config{
		Address: dialHost,
		Scheme:  dialScheme,
		Token:   os.Getenv("CONSUL_TOKEN"),
	})
	if consulErr != nil {
		return nil, consulErr
	}
	return client, nil
}

func GetConsulKey(mooncoreKey string) (string, error) {
	ns := ConsulNamespace()
	key := "mooncore/" + ns + "/" + mooncoreKey
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	kvPair, _, err := client.KV().Get(key, nil)
	if err != nil {
		return "", err
	}
	if kvPair == nil || kvPair.Value == nil {
		return "", fmt.Errorf("Key not found")
	}
	return string(kvPair.Value), nil
}

func ConsulNamespace() string {
	return os.Getenv("CONSUL_NAMESPACE")
}

func consulDialstring(serviceName string) (string, string, error) {
	consulMaster := ""
	if consulMaster = os.Getenv("CONSUL_MASTER"); consulMaster != "" {
		parsed, err := url.Parse(consulMaster)
		if err == nil {
			return parsed.Scheme, parsed.Host, nil
		}
	}
	return "", "", errors.New("CONSUL_MASTER not found or invalid url")
}

func schemaAndPortForServices() (string, int) {
	if ForceHTTP() {
		return "http", 80
	}
	return "https", 443
}
<|endoftext|>


<commit_before>package proxystorage

import (
	"context"
	"math/rand"
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/jacksontj/promxy/proxyquerier"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/metric"
)

func NewProxyStorage(c *Config) (*ProxyStorage, error) {
	// TODO: validate config
	return &ProxyStorage{
		ServerGroups: c.ServerGroups,
	}, nil
}

// TODO: rename?
// TODO: move to its own package?
type ProxyStorage struct {
	// Groups of servers to connect to
	ServerGroups [][]string
}

// Handler to proxy requests to *a* server in serverGroups
func (p *ProxyStorage) ProxyHandler(w http.ResponseWriter, r *http.Request) {

	serverGroup := p.ServerGroups[rand.Int()%len(p.ServerGroups)]
	server := serverGroup[rand.Int()%len(serverGroup)]
	// TODO: failover
	parsedUrl, _ := url.Parse(server)

	proxy := httputil.NewSingleHostReverseProxy(parsedUrl)
	proxy.ServeHTTP(w, r)
}

func (p *ProxyStorage) Querier() (local.Querier, error) {
	return &proxyquerier.ProxyQuerier{p.ServerGroups}, nil
}

// TODO: IMPLEMENT

// Append appends a sample to the underlying storage. Depending on the
// storage implementation, there are different guarantees for the fate
// of the sample after Append has returned. Remote storage
// implementation will simply drop samples if they cannot keep up with
// sending samples. Local storage implementations will only drop metrics
// upon unrecoverable errors.
func (p *ProxyStorage) Append(*model.Sample) error { return nil }

// NeedsThrottling returns true if the underlying storage wishes to not
// receive any more samples. Append will still work but might lead to
// undue resource usage. It is recommended to call NeedsThrottling once
// before an upcoming batch of Append calls (e.g. a full scrape of a
// target or the evaluation of a rule group) and only proceed with the
// batch if NeedsThrottling returns false. In that way, the result of a
// scrape or of an evaluation of a rule group will always be appended
// completely or not at all, and the work of scraping or evaluation will
// not be performed in vain. Also, a call of NeedsThrottling is
// potentially expensive, so limiting the number of calls is reasonable.
//
// Only SampleAppenders for which it is considered critical to receive
// each and every sample should ever return true. SampleAppenders that
// tolerate not receiving all samples should always return false and
// instead drop samples as they see fit to avoid overload.
func (p *ProxyStorage) NeedsThrottling() bool { return false }

// Drop all time series associated with the given label matchers. Returns
// the number series that were dropped.
func (p *ProxyStorage) DropMetricsForLabelMatchers(context.Context, ...*metric.LabelMatcher) (int, error) {
	// TODO: implement
	return 0, nil
}

// Run the various maintenance loops in goroutines. Returns when the
// storage is ready to use. Keeps everything running in the background
// until Stop is called.
func (p *ProxyStorage) Start() error {
	return nil
}

// Stop shuts down the Storage gracefully, flushes all pending
// operations, stops all maintenance loops,and frees all resources.
func (p *ProxyStorage) Stop() error {
	return nil
}

// WaitForIndexing returns once all samples in the storage are
// indexed. Indexing is needed for FingerprintsForLabelMatchers and
// LabelValuesForLabelName and may lag behind.
func (p *ProxyStorage) WaitForIndexing() {}
<commit_msg>Misc cleanup<commit_after>package proxystorage

import (
	"context"
	"math/rand"
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/jacksontj/promxy/proxyquerier"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/metric"
)

func NewProxyStorage(c *Config) (*ProxyStorage, error) {
	// TODO: validate config
	return &ProxyStorage{
		ServerGroups: c.ServerGroups,
	}, nil
}

// TODO: rename?
type ProxyStorage struct {
	// Groups of servers to connect to
	ServerGroups [][]string
}

// Handler to proxy requests to *a* server in serverGroups
func (p *ProxyStorage) ProxyHandler(w http.ResponseWriter, r *http.Request) {
	serverGroup := p.ServerGroups[rand.Int()%len(p.ServerGroups)]
	server := serverGroup[rand.Int()%len(serverGroup)]
	// TODO: failover
	parsedUrl, _ := url.Parse(server)

	proxy := httputil.NewSingleHostReverseProxy(parsedUrl)
	proxy.ServeHTTP(w, r)
}

func (p *ProxyStorage) Querier() (local.Querier, error) {
	return &proxyquerier.ProxyQuerier{p.ServerGroups}, nil
}

// TODO: IMPLEMENT??

// Append appends a sample to the underlying storage. Depending on the
// storage implementation, there are different guarantees for the fate
// of the sample after Append has returned. Remote storage
// implementation will simply drop samples if they cannot keep up with
// sending samples. Local storage implementations will only drop metrics
// upon unrecoverable errors.
func (p *ProxyStorage) Append(*model.Sample) error { return nil }

// NeedsThrottling returns true if the underlying storage wishes to not
// receive any more samples. Append will still work but might lead to
// undue resource usage. It is recommended to call NeedsThrottling once
// before an upcoming batch of Append calls (e.g. a full scrape of a
// target or the evaluation of a rule group) and only proceed with the
// batch if NeedsThrottling returns false. In that way, the result of a
// scrape or of an evaluation of a rule group will always be appended
// completely or not at all, and the work of scraping or evaluation will
// not be performed in vain. Also, a call of NeedsThrottling is
// potentially expensive, so limiting the number of calls is reasonable.
//
// Only SampleAppenders for which it is considered critical to receive
// each and every sample should ever return true. SampleAppenders that
// tolerate not receiving all samples should always return false and
// instead drop samples as they see fit to avoid overload.
func (p *ProxyStorage) NeedsThrottling() bool { return false }

// Drop all time series associated with the given label matchers. Returns
// the number series that were dropped.
func (p *ProxyStorage) DropMetricsForLabelMatchers(context.Context, ...*metric.LabelMatcher) (int, error) {
	// TODO: implement
	return 0, nil
}

// Run the various maintenance loops in goroutines. Returns when the
// storage is ready to use. Keeps everything running in the background
// until Stop is called.
func (p *ProxyStorage) Start() error {
	return nil
}

// Stop shuts down the Storage gracefully, flushes all pending
// operations, stops all maintenance loops,and frees all resources.
func (p *ProxyStorage) Stop() error {
	return nil
}

// WaitForIndexing returns once all samples in the storage are
// indexed. Indexing is needed for FingerprintsForLabelMatchers and
// LabelValuesForLabelName and may lag behind.
func (p *ProxyStorage) WaitForIndexing() {}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/eikeon/hu"
	"github.com/eikeon/hue"
	"github.com/eikeon/scheduler"
	"golang.org/x/net/websocket"
)

const DEV string = "C040BHR3K"

type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type Message struct {
	// Event
	Id        int    `json:"id"`
	Type      string `json:"type"`
	Error     *Error `json:"error,omitempty"`
	Channel   string `json:"channel"`
	User      string `json:"User,omitempty"`
	Text      string `json:"text"`
	TimeStamp string `json:"ts,omitempty"`

	//Confirmation
	Ok      bool `json:"ok"`
	ReplyTo int  `json:"reply_to"`
}

type RTM struct {
	token         string
	ws            *websocket.Conn
	in, out       chan Message
	ids           chan int
	previousStart time.Time
}

func (r *RTM) start() {
	if time.Now().Sub(r.previousStart) < time.Second {
		time.Sleep(10 * time.Second)
	}
	r.previousStart = time.Now()
	resp, err := http.PostForm("https://slack.com/api/rtm.start", url.Values{"token": {r.token}})
	if err != nil {
		log.Fatal(err)
	}
	dec := json.NewDecoder(resp.Body)
	var v struct {
		Ok       bool   `json:"ok"`
		Url      string `json:"url,omitempty"`
		Channels []struct {
			Id   string `json:"id"`
			Name string `json:"name"`
		} `json:"channels"`
	}
	if err := dec.Decode(&v); err != nil {
		log.Fatal("error decoding")
	}
	log.Printf("start response: %#v\n", v)
	origin := "http://localhost/"
	ws, err := websocket.Dial(v.Url, "", origin)
	if err != nil {
		log.Fatal(err)
	}
	r.ws = ws
}

func (r *RTM) run() {
	go func() {
		r.ids = make(chan int)
		for id := 0; ; id++ {
			r.ids <- id
		}
		close(r.ids)
	}()
	r.start()
	go func() {
		for {
			var m Message
			if err := websocket.JSON.Receive(r.ws, &m); err == nil {
				r.in <- m
			} else if err == io.EOF {
				// TODO: backoff / max retry
				log.Println("EOF... restarting")
				r.start()
			} else {
				log.Println("websocket receive:", err)
				r.start()
			}
		}
	}()
	const duration = 10 * time.Second
	timer := time.NewTimer(duration)
	for {
		select {
		case m := <-r.out:
			err := websocket.JSON.Send(r.ws, m)
			if err != nil {
				log.Println("Error sending message:", err)
			}
			timer.Reset(duration)
		case <-timer.C:
			timer = time.NewTimer(duration)
			websocket.JSON.Send(r.ws, &Message{Type: "ping"})
		}
	}
}

type frame map[hu.Symbol]hu.Term

func (frame frame) Define(variable hu.Symbol, value hu.Term) {
	log.Printf("Define: %v - value: %#v\n", variable, value)
	frame[variable] = value
}

func (frame frame) Set(variable hu.Symbol, value hu.Term) bool {
	_, ok := frame[variable]
	return ok
}

func (frame frame) Get(variable hu.Symbol) (hu.Term, bool) {
	value, ok := frame[variable]
	return value, ok
}

func main() {
	t := os.Getenv("SLACK_TOKEN")
	if t == "" {
		log.Fatal("SLACK_TOKEN not defined")
	}
	r := &RTM{token: t, in: make(chan Message, 50), out: make(chan Message, 50)}
	//environment := hu.NewEnvironment()
	//environment := hu.NewEnvironmentWithFrame(make(frame))
	environment := hu.NewEnvironmentWithFrame(&dbframe{})
	hu.AddDefaultBindings(environment)
	environment.AddPrimitive("HueSetState", r.hueSetState)
	environment.AddPrimitive("in", r.runIn)
	environment.AddPrimitive("at", r.runAt)
	environment.AddPrimitive("schedule", r.schedule)
	environment.Define("blink", hu.String(`{"alert": "select"}`))

	go r.run()
	for e := range r.in {
		switch e.Type {
		case "":
			log.Printf("confirmation: %#v\n", e)
		case "hello":
			r.out <- Message{Type: "ping"}
			r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: "Hello world"}
		case "pong":
			log.Println("pong")
		case "error":
			log.Println("Error: Message -> ", e.Error.Message, "Code -> ", e.Error.Code)
			r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: "Error: " + e.Error.Message}
		case "message":
			go func(m Message) {
				log.Printf("seeing message: %#v\n", m)
				//if strings.HasPrefix(m.Text, "{") {
				input := m.Text
				input = strings.Replace(input, `“`, `"`, -1)
				input = strings.Replace(input, `”`, `"`, -1)
				reader := strings.NewReader(input)
				expression := hu.Read(reader)
				result := environment.Evaluate(expression)
				if result != nil {
					r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: fmt.Sprintf("%v", result)}
				}
				//}
			}(e)
		case "team_migration_started":
			log.Printf("team migration started")
			r.start()
		case "user_typing":
			log.Println("User typing")
		case "presence_change":
			log.Println("Presence change")
		default:
			fmt.Printf("Received: %s.\n", e)
		}
	}
}

func (r *RTM) hueSetState(environment *hu.Environment, term hu.Term) hu.Term {
	terms := term.(hu.Tuple)
	address := environment.Evaluate(terms[0])
	value := environment.Evaluate(terms[1])
	log.Printf("hueSetState: %#v: %v", address, value)
	h := &hue.Hue{Username: "28dd21d2f61467f1d0cf7a01b9725f"}
	for {
		var state map[string]interface{}
		dec := json.NewDecoder(strings.NewReader(value.String()))
		if err := dec.Decode(&state); err != nil {
			log.Println("hue decode err:", err)
		}

		err := h.Set(address.String(), &state)
		if err != nil {
			log.Println("error:", err)
			if err := h.CreateUser(h.Username, "Marvin"); err == nil {
				log.Println("h:", h)
			} else {
				text := fmt.Sprintf("%s: press hue link button to authenticate", err)
				log.Println(text)
				r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: text}
				time.Sleep(time.Second)
			}
		} else {
			return nil
		}
	}
	return nil
	//return &Number{result}
}

func (r *RTM) runIn(environment *hu.Environment, term hu.Term) hu.Term {
	terms := term.(hu.Tuple)
	d := environment.Evaluate(terms[0]).(hu.Term).String()
	action := terms[1]
	wait, err := time.ParseDuration(d)
	if err != nil {
		log.Println("err: ", err)
		return nil
	}
	t := time.Now().Add(wait)
	return r.runAtTime(environment, action, t)
}

func (r *RTM) runAt(environment *hu.Environment, term hu.Term) hu.Term {
	terms := term.(hu.Tuple)
	when := environment.Evaluate(terms[0]).(hu.Term).String()
	action := terms[1]
	now := time.Now()
	zone, _ := now.Zone()
	on, err := time.Parse("2006-01-02 "+time.Kitchen+" MST", now.Format("2006-01-02 ")+when+" "+zone)
	if err != nil {
		log.Println("could not parse when of '" + when + "' for " + action.String())
		return nil
	}
	duration := 60 * 60 * 24 * time.Second
	wait := time.Duration((on.UnixNano() - now.UnixNano()) % int64(duration))
	if wait < 0 {
		wait += duration
	}

	t := now.Add(wait)
	return r.runAtTime(environment, action, t)
}

func (r *RTM) runAtTime(environment *hu.Environment, application hu.Term, t time.Time) hu.Term {
	r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: fmt.Sprintf("scheduled `%s` to run at %s", application, t.Format("Monday, January 2, 3:04pm"))}
	wait := time.Duration((t.UnixNano() - time.Now().UnixNano()))
	time.AfterFunc(wait, func() {
		r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: fmt.Sprintf("As requested running `%s` now", application)}
		environment.Evaluate(application)
	})
	return nil
}

func (r *RTM) schedule(environment *hu.Environment, term hu.Term) hu.Term {
	var s scheduler.Schedule
	s.Run()
	return nil
}
<commit_msg>Respond when addressed; respond origin channel<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/eikeon/hu"
	"github.com/eikeon/hue"
	"github.com/eikeon/scheduler"
	"golang.org/x/net/websocket"
)

const DEV string = "C040BHR3K"

type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type Message struct {
	// Event
	Id        int    `json:"id"`
	Type      string `json:"type"`
	Error     *Error `json:"error,omitempty"`
	Channel   string `json:"channel"`
	User      string `json:"User,omitempty"`
	Text      string `json:"text"`
	TimeStamp string `json:"ts,omitempty"`

	//Confirmation
	Ok      bool `json:"ok"`
	ReplyTo int  `json:"reply_to"`
}

type RTM struct {
	token         string
	ws            *websocket.Conn
	in, out       chan Message
	ids           chan int
	previousStart time.Time
}

func (r *RTM) start() {
	if time.Now().Sub(r.previousStart) < time.Second {
		time.Sleep(10 * time.Second)
	}
	r.previousStart = time.Now()
	resp, err := http.PostForm("https://slack.com/api/rtm.start", url.Values{"token": {r.token}})
	if err != nil {
		log.Fatal(err)
	}
	dec := json.NewDecoder(resp.Body)
	var v struct {
		Ok       bool   `json:"ok"`
		Url      string `json:"url,omitempty"`
		Channels []struct {
			Id   string `json:"id"`
			Name string `json:"name"`
		} `json:"channels"`
	}
	if err := dec.Decode(&v); err != nil {
		log.Fatal("error decoding")
	}
	log.Printf("start response: %#v\n", v)
	origin := "http://localhost/"
	ws, err := websocket.Dial(v.Url, "", origin)
	if err != nil {
		log.Fatal(err)
	}
	r.ws = ws
}

func (r *RTM) run() {
	go func() {
		r.ids = make(chan int)
		for id := 0; ; id++ {
			r.ids <- id
		}
		close(r.ids)
	}()
	r.start()
	go func() {
		for {
			var m Message
			if err := websocket.JSON.Receive(r.ws, &m); err == nil {
				r.in <- m
			} else if err == io.EOF {
				// TODO: backoff / max retry
				log.Println("EOF... restarting")
				r.start()
			} else {
				log.Println("websocket receive:", err)
				r.start()
			}
		}
	}()
	const duration = 10 * time.Second
	timer := time.NewTimer(duration)
	for {
		select {
		case m := <-r.out:
			err := websocket.JSON.Send(r.ws, m)
			if err != nil {
				log.Println("Error sending message:", err)
			}
			timer.Reset(duration)
		case <-timer.C:
			timer = time.NewTimer(duration)
			websocket.JSON.Send(r.ws, &Message{Type: "ping"})
		}
	}
}

type frame map[hu.Symbol]hu.Term

func (frame frame) Define(variable hu.Symbol, value hu.Term) {
	log.Printf("Define: %v - value: %#v\n", variable, value)
	frame[variable] = value
}

func (frame frame) Set(variable hu.Symbol, value hu.Term) bool {
	_, ok := frame[variable]
	return ok
}

func (frame frame) Get(variable hu.Symbol) (hu.Term, bool) {
	value, ok := frame[variable]
	return value, ok
}

func main() {
	t := os.Getenv("SLACK_TOKEN")
	if t == "" {
		log.Fatal("SLACK_TOKEN not defined")
	}
	r := &RTM{token: t, in: make(chan Message, 50), out: make(chan Message, 50)}
	//environment := hu.NewEnvironment()
	//environment := hu.NewEnvironmentWithFrame(make(frame))
	environment := hu.NewEnvironmentWithFrame(&dbframe{})
	hu.AddDefaultBindings(environment)
	environment.AddPrimitive("HueSetState", r.hueSetState)
	environment.AddPrimitive("in", r.runIn)
	environment.AddPrimitive("at", r.runAt)
	environment.AddPrimitive("schedule", r.schedule)
	environment.Define("blink", hu.String(`{"alert": "select"}`))

	go r.run()
	for e := range r.in {
		switch e.Type {
		case "":
			log.Printf("confirmation: %#v\n", e)
		case "hello":
			r.out <- Message{Type: "ping"}
			r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: "Hello world"}
		case "pong":
			log.Println("pong")
		case "error":
			log.Println("Error: Message -> ", e.Error.Message, "Code -> ", e.Error.Code)
			r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: "Error: " + e.Error.Message}
		case "message":
			go func(m Message) {
				log.Printf("seeing message: %#v\n", m)
				var input string
				if strings.HasPrefix(m.Channel, "D") {
					input = m.Text
				} else {
					const prefix = "<@U03V77HBT>: "
					log.Printf("%v\n", strings.HasPrefix(m.Text, prefix))
					if strings.HasPrefix(m.Text, prefix) {
						input = m.Text[len(prefix):]
					}
				}
				if input != "" {
					input = strings.Replace(input, `“`, `"`, -1)
					input = strings.Replace(input, `”`, `"`, -1)
					reader := strings.NewReader(input)
					expression := hu.Read(reader)
					result := environment.Evaluate(expression)
					if result != nil {
						r.out <- Message{Id: <-r.ids, Type: "message", Channel: m.Channel, Text: fmt.Sprintf("%v", result)}
					}
				}
			}(e)
		case "team_migration_started":
			log.Printf("team migration started")
			r.start()
		case "user_typing":
			log.Println("User typing")
		case "presence_change":
			log.Println("Presence change")
		default:
			fmt.Printf("Received: %s.\n", e)
		}
	}
}

func (r *RTM) hueSetState(environment *hu.Environment, term hu.Term) hu.Term {
	terms := term.(hu.Tuple)
	address := environment.Evaluate(terms[0])
	value := environment.Evaluate(terms[1])
	log.Printf("hueSetState: %#v: %v", address, value)
	h := &hue.Hue{Username: "28dd21d2f61467f1d0cf7a01b9725f"}
	for {
		var state map[string]interface{}
		dec := json.NewDecoder(strings.NewReader(value.String()))
		if err := dec.Decode(&state); err != nil {
			log.Println("hue decode err:", err)
		}

		err := h.Set(address.String(), &state)
		if err != nil {
			log.Println("error:", err)
			if err := h.CreateUser(h.Username, "Marvin"); err == nil {
				log.Println("h:", h)
			} else {
				text := fmt.Sprintf("%s: press hue link button to authenticate", err)
				log.Println(text)
				r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: text}
				time.Sleep(time.Second)
			}
		} else {
			return nil
		}
	}
	return nil
	//return &Number{result}
}

func (r *RTM) runIn(environment *hu.Environment, term hu.Term) hu.Term {
	terms := term.(hu.Tuple)
	d := environment.Evaluate(terms[0]).(hu.Term).String()
	action := terms[1]
	wait, err := time.ParseDuration(d)
	if err != nil {
		log.Println("err: ", err)
		return nil
	}
	t := time.Now().Add(wait)
	return r.runAtTime(environment, action, t)
}

func (r *RTM) runAt(environment *hu.Environment, term hu.Term) hu.Term {
	terms := term.(hu.Tuple)
	when := environment.Evaluate(terms[0]).(hu.Term).String()
	action := terms[1]
	now := time.Now()
	zone, _ := now.Zone()
	on, err := time.Parse("2006-01-02 "+time.Kitchen+" MST", now.Format("2006-01-02 ")+when+" "+zone)
	if err != nil {
		log.Println("could not parse when of '" + when + "' for " + action.String())
		return nil
	}
	duration := 60 * 60 * 24 * time.Second
	wait := time.Duration((on.UnixNano() - now.UnixNano()) % int64(duration))
	if wait < 0 {
		wait += duration
	}

	t := now.Add(wait)
	return r.runAtTime(environment, action, t)
}

func (r *RTM) runAtTime(environment *hu.Environment, application hu.Term, t time.Time) hu.Term {
	r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: fmt.Sprintf("scheduled `%s` to run at %s", application, t.Format("Monday, January 2, 3:04pm"))}
	wait := time.Duration((t.UnixNano() - time.Now().UnixNano()))
	time.AfterFunc(wait, func() {
		r.out <- Message{Id: <-r.ids, Type: "message", Channel: DEV, Text: fmt.Sprintf("As requested running `%s` now", application)}
		environment.Evaluate(application)
	})
	return nil
}

func (r *RTM) schedule(environment *hu.Environment, term hu.Term) hu.Term {
	var s scheduler.Schedule
	s.Run()
	return nil
}
<|endoftext|>