<commit_before>package server

import (
	"github.com/golang/protobuf/proto"
	"github.com/streadway/amqp"
	"golang.org/x/net/context"

	"github.com/b2aio/typhon/client"
)

type AMQPRequest struct {
	context.Context
	body     interface{}
	delivery *amqp.Delivery

	id              string
	contentType     string
	contentEncoding string
	service         string
	endpoint        string
}

func NewAMQPRequest(delivery *amqp.Delivery) *AMQPRequest {

	contentType, _ := delivery.Headers["Content-Type"].(string)
	contentEncoding, _ := delivery.Headers["Content-Encoding"].(string)
	service, _ := delivery.Headers["Service"].(string)
	endpoint, _ := delivery.Headers["Endpoint"].(string)

	return &AMQPRequest{
		delivery: delivery,

		id:              delivery.CorrelationId,
		contentType:     contentType,
		contentEncoding: contentEncoding,
		service:         service,
		endpoint:        endpoint,
	}
}

// RabbitMQ / AMQP fields

func (r *AMQPRequest) Id() string {
	return r.id
}

func (r *AMQPRequest) Service() string {
	return r.service
}

func (r *AMQPRequest) Endpoint() string {
	return r.endpoint
}

func (r *AMQPRequest) Payload() []byte {
	return r.delivery.Body
}

func (r *AMQPRequest) Body() interface{} {
	return r.body
}

func (r *AMQPRequest) SetBody(body interface{}) {
	r.body = body
}

func (r *AMQPRequest) CorrelationID() string {
	return r.id
}

func (r *AMQPRequest) ReplyTo() string {
	return r.delivery.ReplyTo
}

func (r *AMQPRequest) RoutingKey() string {
	return r.delivery.RoutingKey
}

func (r *AMQPRequest) Interface() interface{} {
	return r.delivery
}

// Client implementation

// ScopedRequest calls a service within the scope of the current request
// This allows request context to be passed transparently,
// and child requests to be 'scoped' within a parent request
func (r *AMQPRequest) ScopedRequest(service string, endpoint string, req proto.Message, resp proto.Message) error {
	// Temporarily just call the default client
	// This means we can nail down our external interface, and work the internals out properly
	// where we can initialise a 'client' and separate out the connected transport layer
	// a client in this case would allow multiple parallel requests etc.
	return client.Req(r, service, endpoint, req, resp)
}
<commit_msg>Add missing methods to AMQP request to satisfy interface<commit_after>package server

import (
	"github.com/golang/protobuf/proto"
	"github.com/streadway/amqp"
	"golang.org/x/net/context"

	"github.com/b2aio/typhon/client"
)

type AMQPRequest struct {
	context.Context
	body     interface{}
	delivery *amqp.Delivery

	id              string
	contentType     string
	contentEncoding string
	service         string
	endpoint        string
}

func NewAMQPRequest(delivery *amqp.Delivery) *AMQPRequest {

	contentType, _ := delivery.Headers["Content-Type"].(string)
	contentEncoding, _ := delivery.Headers["Content-Encoding"].(string)
	service, _ := delivery.Headers["Service"].(string)
	endpoint, _ := delivery.Headers["Endpoint"].(string)

	return &AMQPRequest{
		delivery: delivery,

		id:              delivery.CorrelationId,
		contentType:     contentType,
		contentEncoding: contentEncoding,
		service:         service,
		endpoint:        endpoint,
	}
}

// RabbitMQ / AMQP fields

func (r *AMQPRequest) Id() string {
	return r.id
}

func (r *AMQPRequest) ContentType() string {
	return r.contentType
}

func (r *AMQPRequest) ContentEncoding() string {
	return r.contentEncoding
}

func (r *AMQPRequest) Service() string {
	return r.service
}

func (r *AMQPRequest) Endpoint() string {
	return r.endpoint
}

func (r *AMQPRequest) Payload() []byte {
	return r.delivery.Body
}

func (r *AMQPRequest) Body() interface{} {
	return r.body
}

func (r *AMQPRequest) SetBody(body interface{}) {
	r.body = body
}

func (r *AMQPRequest) CorrelationID() string {
	return r.id
}

func (r *AMQPRequest) ReplyTo() string {
	return r.delivery.ReplyTo
}

func (r *AMQPRequest) RoutingKey() string {
	return r.delivery.RoutingKey
}

func (r *AMQPRequest) Interface() interface{} {
	return r.delivery
}

// Client implementation

// ScopedRequest calls a service within the scope of the current request
// This allows request context to be passed transparently,
// and child requests to be 'scoped' within a parent request
func (r *AMQPRequest) ScopedRequest(service string, endpoint string, req proto.Message, resp proto.Message) error {
	// Temporarily just call the default client
	// This means we can nail down our external interface, and work the internals out properly
	// where we can initialise a 'client' and separate out the connected transport layer
	// a client in this case would allow multiple parallel requests etc.
	return client.Req(r, service, endpoint, req, resp)
}
<|endoftext|>


<commit_before>package image

import (
	"bytes"
	"encoding/base64"
	"github.com/labstack/echo/v4"
	"golang.org/x/image/draw"
	goimg "image"
	"image/color"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"net/http"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/fileloader"
)

const (
	defaultThumbnailWidth  = 24
	defaultThumbnailHeight = 24
	defaultQuality         = 85
	defaultThumbQuality    = 50
)

type (
	Size struct {
		W, H int
	}

	// Image is an open image file
	Image interface {
		goimg.Image
		Duplicate() Image
		Size() Size
		Draw(img Image, x, y int)
		Resize(width, height int)
		ResizeFit(width, height int)
		ResizeLimit(width, height int)
		Crop(bounds goimg.Rectangle)
		ResizeFill(width, height int)
		ToJpeg(quality int) (*bytes.Buffer, error)
		ToPng(level PngCompressionOpt) (*bytes.Buffer, error)
		ToBase64(quality int) (string, error)
	}

	imageData struct {
		img draw.Image
	}

	// Options represent the image options of the loaded image
	Options struct {
		Width        int
		Height       int
		ThumbWidth   int
		ThumbHeight  int
		Quality      int
		ThumbQuality int
		Fill         bool
	}
)

func FromImage(img goimg.Image) Image {
	bounds := img.Bounds()
	target := goimg.NewNRGBA64(bounds)
	draw.Draw(target, target.Bounds(), img, bounds.Min, draw.Src)
	return &imageData{
		img: target,
	}
}

func FromJpeg(file io.Reader) (Image, error) {
	i, err := jpeg.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid JPEG image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

func FromPng(file io.Reader) (Image, error) {
	i, err := png.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid PNG image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

func FromGif(file io.Reader) (Image, error) {
	i, err := gif.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid GIF image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

const (
	// MediaTypeJpeg is the mime type for jpeg images
	MediaTypeJpeg = "image/jpeg"
	// MediaTypePng is the mime type for png images
	MediaTypePng = "image/png"
	// MediaTypeGif is the mime type for gif images
	MediaTypeGif = "image/gif"
)

func LoadImage(c echo.Context, formField string) (Image, error) {
	file, mediaType, _, err := fileloader.LoadOpenFile(c, formField, []string{MediaTypePng, MediaTypeJpeg, MediaTypeGif})
	if err != nil {
		return nil, governor.NewErrorUser("Invalid image file", http.StatusBadRequest, err)
	}
	defer func() {
		err := file.Close()
		if err != nil {
		}
	}()
	switch mediaType {
	case MediaTypeJpeg:
		return FromJpeg(file)
	case MediaTypePng:
		return FromPng(file)
	case MediaTypeGif:
		return FromGif(file)
	default:
		return nil, governor.NewErrorUser("Invalid file type", http.StatusBadRequest, err)
	}
}

func (i imageData) ColorModel() color.Model {
	return i.img.ColorModel()
}

func (i imageData) Bounds() goimg.Rectangle {
	return i.img.Bounds()
}

func (i imageData) At(x, y int) color.Color {
	return i.img.At(x, y)
}

func (i imageData) Duplicate() Image {
	return FromImage(i.img)
}

func (i imageData) Size() Size {
	k := i.img.Bounds().Size()
	return Size{
		W: k.X,
		H: k.Y,
	}
}

func (i *imageData) Draw(img Image, x, y int) {
	source := img.Bounds()
	draw.Draw(i.img, source.Sub(source.Min).Add(i.img.Bounds().Min).Add(goimg.Pt(x, y)), img, source.Min, draw.Src)
}

func (i *imageData) Resize(width, height int) {
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, i.img.Bounds(), draw.Src, nil)
	i.img = target
}

func dimensionsFit(fromWidth, fromHeight, toWidth, toHeight int) (int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// height is fit
		return fromWidth * toHeight / fromHeight, toHeight
	} else {
		// width is fit
		return toWidth, fromHeight * toWidth / fromWidth
	}
}

func (i *imageData) ResizeFit(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight := dimensionsFit(s.X, s.Y, width, height)
	i.Resize(targetWidth, targetHeight)
}

func (i *imageData) ResizeLimit(width, height int) {
	s := i.img.Bounds().Size()
	if s.X < width && s.Y < height {
		return
	}
	i.ResizeFit(width, height)
}

func (i *imageData) Crop(bounds goimg.Rectangle) {
	size := bounds.Size()
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, size.X, size.Y))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.Draw(target, target.Bounds(), i.img, bounds.Min, draw.Src)
	i.img = target
}

func maxInt(a, b int) int {
	if a < b {
		return b
	}
	return a
}

func dimensionsFill(fromWidth, fromHeight, toWidth, toHeight int) (int, int, int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// width is fit
		height := toHeight * fromWidth / toWidth
		return fromWidth, height, 0, maxInt((fromHeight-height)/2, 0)
	} else {
		// height is fit
		width := toWidth * fromHeight / toHeight
		return width, fromHeight, maxInt((fromWidth-width)/2, 0), 0
	}
}

func (i *imageData) ResizeFill(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight, offsetX, offsetY := dimensionsFill(s.X, s.Y, width, height)
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, goimg.Rect(offsetX, offsetY, offsetX+targetWidth, offsetY+targetHeight), draw.Src, nil)
	i.img = target
}

func (i *imageData) ToJpeg(quality int) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	j := jpeg.Options{
		Quality: quality,
	}
	if err := jpeg.Encode(b, i.img, &j); err != nil {
		return nil, governor.NewError("Failed to encode JPEG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

type PngCompressionOpt int

const (
	PngDefault PngCompressionOpt = iota
	PngNone
	PngFast
	PngBest
)

func compressionOptTranslate(level PngCompressionOpt) png.CompressionLevel {
	switch level {
	case PngDefault:
		return png.DefaultCompression
	case PngNone:
		return png.NoCompression
	case PngFast:
		return png.BestSpeed
	case PngBest:
		return png.BestCompression
	default:
		return png.DefaultCompression
	}
}

func (i *imageData) ToPng(level PngCompressionOpt) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	encoder := png.Encoder{
		CompressionLevel: compressionOptTranslate(level),
	}
	if err := encoder.Encode(b, i.img); err != nil {
		return nil, governor.NewError("Failed to encode PNG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

const (
	dataURIPrefixJpeg = "data:image/jpeg;base64,"
)

func (i *imageData) ToBase64(quality int) (string, error) {
	b, err := i.ToJpeg(quality)
	if err != nil {
		return "", err
	}
	return dataURIPrefixJpeg + base64.RawStdEncoding.EncodeToString(b.Bytes()), nil
}
<commit_msg>Remove unused constants<commit_after>package image

import (
	"bytes"
	"encoding/base64"
	"github.com/labstack/echo/v4"
	"golang.org/x/image/draw"
	goimg "image"
	"image/color"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"net/http"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/fileloader"
)

type (
	Size struct {
		W, H int
	}

	// Image is an open image file
	Image interface {
		goimg.Image
		Duplicate() Image
		Size() Size
		Draw(img Image, x, y int)
		Resize(width, height int)
		ResizeFit(width, height int)
		ResizeLimit(width, height int)
		Crop(bounds goimg.Rectangle)
		ResizeFill(width, height int)
		ToJpeg(quality int) (*bytes.Buffer, error)
		ToPng(level PngCompressionOpt) (*bytes.Buffer, error)
		ToBase64(quality int) (string, error)
	}

	imageData struct {
		img draw.Image
	}

	// Options represent the image options of the loaded image
	Options struct {
		Width        int
		Height       int
		ThumbWidth   int
		ThumbHeight  int
		Quality      int
		ThumbQuality int
		Fill         bool
	}
)

func FromImage(img goimg.Image) Image {
	bounds := img.Bounds()
	target := goimg.NewNRGBA64(bounds)
	draw.Draw(target, target.Bounds(), img, bounds.Min, draw.Src)
	return &imageData{
		img: target,
	}
}

func FromJpeg(file io.Reader) (Image, error) {
	i, err := jpeg.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid JPEG image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

func FromPng(file io.Reader) (Image, error) {
	i, err := png.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid PNG image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

func FromGif(file io.Reader) (Image, error) {
	i, err := gif.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid GIF image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

const (
	// MediaTypeJpeg is the mime type for jpeg images
	MediaTypeJpeg = "image/jpeg"
	// MediaTypePng is the mime type for png images
	MediaTypePng = "image/png"
	// MediaTypeGif is the mime type for gif images
	MediaTypeGif = "image/gif"
)

func LoadImage(c echo.Context, formField string) (Image, error) {
	file, mediaType, _, err := fileloader.LoadOpenFile(c, formField, []string{MediaTypePng, MediaTypeJpeg, MediaTypeGif})
	if err != nil {
		return nil, governor.NewErrorUser("Invalid image file", http.StatusBadRequest, err)
	}
	defer func() {
		err := file.Close()
		if err != nil {
		}
	}()
	switch mediaType {
	case MediaTypeJpeg:
		return FromJpeg(file)
	case MediaTypePng:
		return FromPng(file)
	case MediaTypeGif:
		return FromGif(file)
	default:
		return nil, governor.NewErrorUser("Invalid file type", http.StatusBadRequest, err)
	}
}

func (i imageData) ColorModel() color.Model {
	return i.img.ColorModel()
}

func (i imageData) Bounds() goimg.Rectangle {
	return i.img.Bounds()
}

func (i imageData) At(x, y int) color.Color {
	return i.img.At(x, y)
}

func (i imageData) Duplicate() Image {
	return FromImage(i.img)
}

func (i imageData) Size() Size {
	k := i.img.Bounds().Size()
	return Size{
		W: k.X,
		H: k.Y,
	}
}

func (i *imageData) Draw(img Image, x, y int) {
	source := img.Bounds()
	draw.Draw(i.img, source.Sub(source.Min).Add(i.img.Bounds().Min).Add(goimg.Pt(x, y)), img, source.Min, draw.Src)
}

func (i *imageData) Resize(width, height int) {
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, i.img.Bounds(), draw.Src, nil)
	i.img = target
}

func dimensionsFit(fromWidth, fromHeight, toWidth, toHeight int) (int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// height is fit
		return fromWidth * toHeight / fromHeight, toHeight
	} else {
		// width is fit
		return toWidth, fromHeight * toWidth / fromWidth
	}
}

func (i *imageData) ResizeFit(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight := dimensionsFit(s.X, s.Y, width, height)
	i.Resize(targetWidth, targetHeight)
}

func (i *imageData) ResizeLimit(width, height int) {
	s := i.img.Bounds().Size()
	if s.X < width && s.Y < height {
		return
	}
	i.ResizeFit(width, height)
}

func (i *imageData) Crop(bounds goimg.Rectangle) {
	size := bounds.Size()
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, size.X, size.Y))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.Draw(target, target.Bounds(), i.img, bounds.Min, draw.Src)
	i.img = target
}

func maxInt(a, b int) int {
	if a < b {
		return b
	}
	return a
}

func dimensionsFill(fromWidth, fromHeight, toWidth, toHeight int) (int, int, int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// width is fit
		height := toHeight * fromWidth / toWidth
		return fromWidth, height, 0, maxInt((fromHeight-height)/2, 0)
	} else {
		// height is fit
		width := toWidth * fromHeight / toHeight
		return width, fromHeight, maxInt((fromWidth-width)/2, 0), 0
	}
}

func (i *imageData) ResizeFill(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight, offsetX, offsetY := dimensionsFill(s.X, s.Y, width, height)
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, goimg.Rect(offsetX, offsetY, offsetX+targetWidth, offsetY+targetHeight), draw.Src, nil)
	i.img = target
}

func (i *imageData) ToJpeg(quality int) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	j := jpeg.Options{
		Quality: quality,
	}
	if err := jpeg.Encode(b, i.img, &j); err != nil {
		return nil, governor.NewError("Failed to encode JPEG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

type PngCompressionOpt int

const (
	PngDefault PngCompressionOpt = iota
	PngNone
	PngFast
	PngBest
)

func compressionOptTranslate(level PngCompressionOpt) png.CompressionLevel {
	switch level {
	case PngDefault:
		return png.DefaultCompression
	case PngNone:
		return png.NoCompression
	case PngFast:
		return png.BestSpeed
	case PngBest:
		return png.BestCompression
	default:
		return png.DefaultCompression
	}
}

func (i *imageData) ToPng(level PngCompressionOpt) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	encoder := png.Encoder{
		CompressionLevel: compressionOptTranslate(level),
	}
	if err := encoder.Encode(b, i.img); err != nil {
		return nil, governor.NewError("Failed to encode PNG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

const (
	dataURIPrefixJpeg = "data:image/jpeg;base64,"
)

func (i *imageData) ToBase64(quality int) (string, error) {
	b, err := i.ToJpeg(quality)
	if err != nil {
		return "", err
	}
	return dataURIPrefixJpeg + base64.RawStdEncoding.EncodeToString(b.Bytes()), nil
}
<|endoftext|>


<commit_before>package hh

import (
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"time"

	"github.com/influxdb/influxdb/tsdb"
)

var ErrHintedHandoffDisabled = fmt.Errorf("hinted handoff disabled")

type Service struct {
	mu      sync.RWMutex
	closing chan struct{}

	Logger *log.Logger
	cfg    Config

	ShardWriter shardWriter

	HintedHandoff interface {
		WriteShard(shardID, ownerID uint64, points []tsdb.Point) error
		Process() error
		PurgeOlderThan(when time.Duration) error
	}
}

type shardWriter interface {
	WriteShard(shardID, ownerID uint64, points []tsdb.Point) error
}

// NewService returns a new instance of Service.
func NewService(c Config, w shardWriter) *Service {
	s := &Service{
		cfg:    c,
		Logger: log.New(os.Stderr, "[handoff] ", log.LstdFlags),
	}
	processor, err := NewProcessor(c.Dir, w, ProcessorOptions{
		MaxSize:        c.MaxSize,
		RetryRateLimit: c.RetryRateLimit,
	})
	if err != nil {
		s.Logger.Fatalf("Failed to start hinted handoff processor: %v", err)
	}
	s.HintedHandoff = processor
	return s
}

func (s *Service) Open() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.closing = make(chan struct{})

	go s.retryWrites()

	go s.expireWrites()
	// go s.evictWrites()

	return nil
}

func (s *Service) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closing != nil {
		close(s.closing)
		s.closing = nil
	}
	return nil
}

// WriteShard queues the points write for shardID to node ownerID to handoff queue
func (s *Service) WriteShard(shardID, ownerID uint64, points []tsdb.Point) error {
	if !s.cfg.Enabled {
		return ErrHintedHandoffDisabled
	}

	return s.HintedHandoff.WriteShard(shardID, ownerID, points)
}

func (s *Service) retryWrites() {
	ticker := time.NewTicker(time.Duration(s.cfg.RetryInterval))
	defer ticker.Stop()
	for {
		select {
		case <-s.closing:
			return
		case <-ticker.C:
			if err := s.HintedHandoff.Process(); err != nil && err != io.EOF {
				s.Logger.Printf("retried write failed: %v", err)
			}
		}
	}
}

// expireWrites will cause the handoff queues to remove writes that are older
// than the configured threshold
func (s *Service) expireWrites() {
	ticker := time.NewTicker(time.Hour)
	defer ticker.Stop()
	for {
		select {
		case <-s.closing:
			return
		case <-ticker.C:
			if err := s.HintedHandoff.PurgeOlderThan(time.Duration(s.cfg.MaxAge)); err != nil {
				s.Logger.Printf("purge write failed: %v", err)
			}
		}
	}
}

// purgeWrites will cause the handoff queues to remove writes that are no longer
// valid.  e.g. queued writes for a node that has been removed
func (s *Service) purgeWrites() {
	panic("not implemented")
}
<commit_msg>Fix data race when close hinted handoff service<commit_after>package hh

import (
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"time"

	"github.com/influxdb/influxdb/tsdb"
)

var ErrHintedHandoffDisabled = fmt.Errorf("hinted handoff disabled")

type Service struct {
	mu      sync.RWMutex
	closing chan struct{}

	Logger *log.Logger
	cfg    Config

	ShardWriter shardWriter

	HintedHandoff interface {
		WriteShard(shardID, ownerID uint64, points []tsdb.Point) error
		Process() error
		PurgeOlderThan(when time.Duration) error
	}
}

type shardWriter interface {
	WriteShard(shardID, ownerID uint64, points []tsdb.Point) error
}

// NewService returns a new instance of Service.
func NewService(c Config, w shardWriter) *Service {
	s := &Service{
		cfg:    c,
		Logger: log.New(os.Stderr, "[handoff] ", log.LstdFlags),
	}
	processor, err := NewProcessor(c.Dir, w, ProcessorOptions{
		MaxSize:        c.MaxSize,
		RetryRateLimit: c.RetryRateLimit,
	})
	if err != nil {
		s.Logger.Fatalf("Failed to start hinted handoff processor: %v", err)
	}
	s.HintedHandoff = processor
	return s
}

func (s *Service) Open() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.closing = make(chan struct{})

	go s.retryWrites()

	go s.expireWrites()
	// go s.evictWrites()

	return nil
}

func (s *Service) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closing != nil {
		close(s.closing)
	}
	return nil
}

// WriteShard queues the points write for shardID to node ownerID to handoff queue
func (s *Service) WriteShard(shardID, ownerID uint64, points []tsdb.Point) error {
	if !s.cfg.Enabled {
		return ErrHintedHandoffDisabled
	}

	return s.HintedHandoff.WriteShard(shardID, ownerID, points)
}

func (s *Service) retryWrites() {
	ticker := time.NewTicker(time.Duration(s.cfg.RetryInterval))
	defer ticker.Stop()
	for {
		select {
		case <-s.closing:
			return
		case <-ticker.C:
			if err := s.HintedHandoff.Process(); err != nil && err != io.EOF {
				s.Logger.Printf("retried write failed: %v", err)
			}
		}
	}
}

// expireWrites will cause the handoff queues to remove writes that are older
// than the configured threshold
func (s *Service) expireWrites() {
	ticker := time.NewTicker(time.Hour)
	defer ticker.Stop()
	for {
		select {
		case <-s.closing:
			return
		case <-ticker.C:
			if err := s.HintedHandoff.PurgeOlderThan(time.Duration(s.cfg.MaxAge)); err != nil {
				s.Logger.Printf("purge write failed: %v", err)
			}
		}
	}
}

// purgeWrites will cause the handoff queues to remove writes that are no longer
// valid.  e.g. queued writes for a node that has been removed
func (s *Service) purgeWrites() {
	panic("not implemented")
}
<|endoftext|>


<commit_before>package skip

import (
	"net/http"
	"testing"

	"github.com/google/martian/martian"
	"github.com/google/martian/parse/parse"
)

func TestRoundTrip(t *testing.T) {
	m := NewRoundTrip()
	req, err := http.NewRequest("GET", "http://example.com", nil)
	if err != nil {
		t.Fatalf("http.NewRequest(): got %v, want no error", err)
	}

	ctx, remove, err := martian.TestContext(req)
	if err != nil {
		t.Fatalf("martian.TestContext(): got %v, want no error", err)
	}
	defer remove()

	if ctx.SkippingRoundTrip() {
		t.Fatal("ctx.SkippingRoundTrip(): got true, want false")
	}

	if err := m.ModifyRequest(req); err != nil {
		t.Fatalf("ModifyRequest(): got %v, want no error", err)
	}

	if !ctx.SkippingRoundTrip() {
		t.Fatal("ctx.SkippingRoundTrip(): got false, want true")
	}
}

func TestFromJSON(t *testing.T) {
	msg := []byte(`{
			  "skip.RoundTrip": {}
	        }`)

	r, err := parse.FromJSON(msg)
	if err != nil {
		t.Fatalf("parse.FromJSON(): got %v, want no error", err)
	}

	reqmod := r.RequestModifier()
	if _, ok := reqmod.(*RoundTrip); !ok {
		t.Fatal("reqmod.(*RoundTrip): got !ok, want ok")
	}
}
<commit_msg>one-line the json in the test<commit_after>package skip

import (
	"net/http"
	"testing"

	"github.com/google/martian/martian"
	"github.com/google/martian/parse/parse"
)

func TestRoundTrip(t *testing.T) {
	m := NewRoundTrip()
	req, err := http.NewRequest("GET", "http://example.com", nil)
	if err != nil {
		t.Fatalf("http.NewRequest(): got %v, want no error", err)
	}

	ctx, remove, err := martian.TestContext(req)
	if err != nil {
		t.Fatalf("martian.TestContext(): got %v, want no error", err)
	}
	defer remove()

	if ctx.SkippingRoundTrip() {
		t.Fatal("ctx.SkippingRoundTrip(): got true, want false")
	}

	if err := m.ModifyRequest(req); err != nil {
		t.Fatalf("ModifyRequest(): got %v, want no error", err)
	}

	if !ctx.SkippingRoundTrip() {
		t.Fatal("ctx.SkippingRoundTrip(): got false, want true")
	}
}

func TestFromJSON(t *testing.T) {
	msg := []byte(`{"skip.RoundTrip": {}}`)

	r, err := parse.FromJSON(msg)
	if err != nil {
		t.Fatalf("parse.FromJSON(): got %v, want no error", err)
	}

	reqmod := r.RequestModifier()
	if _, ok := reqmod.(*RoundTrip); !ok {
		t.Fatal("reqmod.(*RoundTrip): got !ok, want ok")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"./slackboard"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

func main() {

	version := flag.Bool("v", false, "slackboard version")
	server := flag.String("s", "", "slackboard server name")
	tag := flag.String("t", "", "slackboard tag name")
	sync := flag.Bool("sync", false, "enable synchronous notification")
	notify := flag.Bool("notity", true, "enable notification to slackboard")
	logfile := flag.String("log", "", "log-file path")
	channel := flag.String("c", "", "slackboard channel name")
	username := flag.String("u", "slackboard", "user name")
	iconemoji := flag.String("i", ":clipboard:", "emoji icon")
	parse := flag.String("p", "full", "parsing mode")
	flag.Parse()

	if *version {
		slackboard.PrintVersion()
		os.Exit(0)
	}

	if *server == "" && *tag == "" {
		flag.PrintDefaults()
		os.Exit(0)
	}

	if *server == "" {
		log.Fatal("Specify slackboard server name")
	}

	if *tag == "" && *channel == "" {
		log.Fatal("Specify slackboard tag or channel name")
	}

	if *tag != "" && *channel != "" {
		log.Fatal("Assigning with '-t' and '-c' at once is not allowed")
	}

	argv := flag.Args()

	if len(argv) == 0 {
		log.Fatal("command is not specified")
	}

	hostname, err := os.Hostname()
	if err != nil {
		hostname = "localhost"
	}

	out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput()
	if err != nil && *notify {
		text := fmt.Sprintf(`
Host   : %s
Command: %s
Output : %s
Error  : %s
`, hostname, strings.Join(argv, " "), string(out), err.Error())

		if *tag != "" {
			payload := &slackboard.SlackboardPayload{
				Tag:  *tag,
				Host: hostname,
				Text: text,
				Sync: *sync,
			}

			err = slackboard.SendNotification2Slackboard(*server, payload)
			if err != nil {
				log.Fatal(err.Error())
			}
		} else {
			payloadSlack := slackboard.SlackPayload{
				Channel:   *channel,
				Username:  *username,
				IconEmoji: *iconemoji,
				Text:      text,
				Parse:     *parse,
			}
			payloadDirectly := &slackboard.SlackboardDirectPayload{
				Payload: payloadSlack,
				Sync:    *sync,
			}

			err = slackboard.SendNotification2SlackboardDirectly(*server, payloadDirectly)
			if err != nil {
				log.Fatal(err.Error())
			}
		}
	}

	if *logfile == "" {
		return
	}

	fi, err := os.Stat(*logfile)
	if err == nil {
		if fi.IsDir() {
			log.Fatalf("%s is a directory.", *logfile)
		}
		file, err := os.OpenFile(*logfile, os.O_RDWR|os.O_APPEND, os.ModePerm)
		if err != nil {
			log.Fatal(err.Error())
		}
		defer file.Close()
		file.WriteString(string(out))
	} else {
		file, err := os.Create(*logfile)
		if err != nil {
			log.Fatal(err.Error())
		}
		defer file.Close()
		file.WriteString(string(out))
	}

}
<commit_msg>typo fixed.<commit_after>package main

import (
	"./slackboard"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

func main() {

	version := flag.Bool("v", false, "slackboard version")
	server := flag.String("s", "", "slackboard server name")
	tag := flag.String("t", "", "slackboard tag name")
	sync := flag.Bool("sync", false, "enable synchronous notification")
	notify := flag.Bool("notify", true, "enable notification to slackboard")
	logfile := flag.String("log", "", "log-file path")
	channel := flag.String("c", "", "slackboard channel name")
	username := flag.String("u", "slackboard", "user name")
	iconemoji := flag.String("i", ":clipboard:", "emoji icon")
	parse := flag.String("p", "full", "parsing mode")
	flag.Parse()

	if *version {
		slackboard.PrintVersion()
		os.Exit(0)
	}

	if *server == "" && *tag == "" {
		flag.PrintDefaults()
		os.Exit(0)
	}

	if *server == "" {
		log.Fatal("Specify slackboard server name")
	}

	if *tag == "" && *channel == "" {
		log.Fatal("Specify slackboard tag or channel name")
	}

	if *tag != "" && *channel != "" {
		log.Fatal("Assigning with '-t' and '-c' at once is not allowed")
	}

	argv := flag.Args()

	if len(argv) == 0 {
		log.Fatal("command is not specified")
	}

	hostname, err := os.Hostname()
	if err != nil {
		hostname = "localhost"
	}

	out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput()
	if err != nil && *notify {
		text := fmt.Sprintf(`
Host   : %s
Command: %s
Output : %s
Error  : %s
`, hostname, strings.Join(argv, " "), string(out), err.Error())

		if *tag != "" {
			payload := &slackboard.SlackboardPayload{
				Tag:  *tag,
				Host: hostname,
				Text: text,
				Sync: *sync,
			}

			err = slackboard.SendNotification2Slackboard(*server, payload)
			if err != nil {
				log.Fatal(err.Error())
			}
		} else {
			payloadSlack := slackboard.SlackPayload{
				Channel:   *channel,
				Username:  *username,
				IconEmoji: *iconemoji,
				Text:      text,
				Parse:     *parse,
			}
			payloadDirectly := &slackboard.SlackboardDirectPayload{
				Payload: payloadSlack,
				Sync:    *sync,
			}

			err = slackboard.SendNotification2SlackboardDirectly(*server, payloadDirectly)
			if err != nil {
				log.Fatal(err.Error())
			}
		}
	}

	if *logfile == "" {
		return
	}

	fi, err := os.Stat(*logfile)
	if err == nil {
		if fi.IsDir() {
			log.Fatalf("%s is a directory.", *logfile)
		}
		file, err := os.OpenFile(*logfile, os.O_RDWR|os.O_APPEND, os.ModePerm)
		if err != nil {
			log.Fatal(err.Error())
		}
		defer file.Close()
		file.WriteString(string(out))
	} else {
		file, err := os.Create(*logfile)
		if err != nil {
			log.Fatal(err.Error())
		}
		defer file.Close()
		file.WriteString(string(out))
	}

}
<|endoftext|>


<commit_before>package server

import (
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/lucas-clemente/goldfish/repository"
	"github.com/skratchdot/open-golang/open"
)

// NewHandler2 makes a http.Handler for a given repo.
func NewHandler2(repo repository.Repo) http.Handler {
	gin.SetMode(gin.ReleaseMode)

	router := gin.New()

	router.GET("/v2/raw/*path", errorHandler(func(c *gin.Context) error {
		path := c.Params.ByName("path")

		f, err := repo.ReadFile(path)
		if err != nil {
			return err
		}

		reader, err := f.Reader()
		if err != nil {
			return err
		}
		defer reader.Close()

		c.Writer.Header().Set("Content-Type", getContentType(path))

		if _, err := io.Copy(c.Writer, reader); err != nil {
			log.Println(err)
		}
		return nil
	}))

	router.POST("/v2/raw/*path", errorHandler(func(c *gin.Context) error {
		path := c.Params.ByName("path")

		err := repo.StoreFile(path, c.Request.Body)
		if err != nil {
			return err
		}

		c.Writer.WriteHeader(http.StatusNoContent)
		return nil
	}))

	router.GET("/v2/folders/:id", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		entries, err := repo.ListFiles(idToPath(id))
		if err != nil {
			return err
		}

		pageIDs := []string{}
		subfolderIDs := []string{}

		for _, entry := range entries {
			entryPath := entry.Path()
			if entryPath[len(entryPath)-1] == '/' {
				subfolderIDs = append(subfolderIDs, pathToID(entryPath[0:len(entryPath)-1]))
			} else {
				pageIDs = append(pageIDs, pathToID(entryPath))
			}
		}

		var parentID interface{}
		if id != "|" {
			parentID = id[0:strings.LastIndex(id, "|")]
			if parentID == "" {
				parentID = "|"
			}
		}

		c.JSON(200, map[string]interface{}{
			"folder": map[string]interface{}{
				"id":           id,
				"pages":        pageIDs,
				"subfolders":   subfolderIDs,
				"parentFolder": parentID,
			},
		})
		return nil
	}))

	router.GET("/v2/pages/:id", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		file, err := repo.ReadFile(idToPath(id))
		if err != nil {
			return err
		}

		jsonPage, err := getPageJSON(file)
		if err != nil {
			return err
		}

		c.JSON(200, map[string]interface{}{"page": jsonPage})
		return nil
	}))

	router.DELETE("/v2/pages/:id", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		if err := repo.DeleteFile(idToPath(id)); err != nil {
			return err
		}
		c.Writer.WriteHeader(http.StatusNoContent)
		return nil
	}))

	router.POST("/v2/pages/:id/open", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		p, err := repo.LocalPathForFile(idToPath(id))

		if err != nil {
			return err
		}

		if err := open.Run(p); err != nil {
			return err
		}

		c.Writer.WriteHeader(http.StatusNoContent)
		return nil
	}))

	router.GET("/v2/pages", errorHandler(func(c *gin.Context) error {
		var searchTerm string
		searchTermList, ok := c.Request.URL.Query()["q"]
		if ok && len(searchTermList) != 0 {
			searchTerm = searchTermList[0]
		}

		results, err := repo.SearchFiles(searchTerm)
		if err != nil {
			return err
		}

		jsonArray := []interface{}{}
		for _, file := range results {
			jsonPage, err := getPageJSON(file)
			if err != nil {
				return err
			}
			jsonArray = append(jsonArray, jsonPage)
		}

		c.JSON(200, map[string]interface{}{"pages": jsonArray})
		return nil
	}))

	return router
}

func idToPath(id string) string {
	return strings.Replace(id, "|", "/", -1)
}

func pathToID(path string) string {
	return strings.Replace(path, "/", "|", -1)
}

func getContentType(filename string) string {
	extension := filename[strings.LastIndex(filename, ".")+1:]
	switch extension {
	case "jpg":
		return "image/jpeg"
	case "png":
		return "image/png"
	case "svg":
		return "image/svg+xml"
	case "pdf":
		return "application/pdf"
	}
	return "text/plain"
}

func getPageJSON(file repository.File) (interface{}, error) {
	id := pathToID(file.Path())

	folder := id[0:strings.LastIndex(id, "|")]
	if folder == "" {
		folder = "|"
	}

	var markdownSource interface{}
	if strings.HasSuffix(id, ".md") {
		c, err := file.Reader()
		if err != nil {
			return nil, err
		}
		defer c.Close()

		markdownSourceBytes, err := ioutil.ReadAll(c)
		if err != nil {
			return nil, err
		}
		markdownSource = string(markdownSourceBytes)
	}

	return (map[string]interface{}{
		"id":             id,
		"folder":         folder,
		"modifiedAt":     file.ModTime(),
		"markdownSource": markdownSource,
	}), nil
}

func errorHandler(h func(*gin.Context) error) gin.HandlerFunc {
	return func(c *gin.Context) {
		if err := h(c); err != nil {
			if os.IsNotExist(err) {
				c.AbortWithError(404, err)
			}
			c.AbortWithError(500, err)
		}
	}
}
<commit_msg>add logging for internal server errors<commit_after>package server

import (
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/lucas-clemente/goldfish/repository"
	"github.com/skratchdot/open-golang/open"
)

// NewHandler2 makes a http.Handler for a given repo.
func NewHandler2(repo repository.Repo) http.Handler {
	gin.SetMode(gin.ReleaseMode)

	router := gin.New()

	router.GET("/v2/raw/*path", errorHandler(func(c *gin.Context) error {
		path := c.Params.ByName("path")

		f, err := repo.ReadFile(path)
		if err != nil {
			return err
		}

		reader, err := f.Reader()
		if err != nil {
			return err
		}
		defer reader.Close()

		c.Writer.Header().Set("Content-Type", getContentType(path))

		if _, err := io.Copy(c.Writer, reader); err != nil {
			log.Println(err)
		}
		return nil
	}))

	router.POST("/v2/raw/*path", errorHandler(func(c *gin.Context) error {
		path := c.Params.ByName("path")

		err := repo.StoreFile(path, c.Request.Body)
		if err != nil {
			return err
		}

		c.Writer.WriteHeader(http.StatusNoContent)
		return nil
	}))

	router.GET("/v2/folders/:id", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		entries, err := repo.ListFiles(idToPath(id))
		if err != nil {
			return err
		}

		pageIDs := []string{}
		subfolderIDs := []string{}

		for _, entry := range entries {
			entryPath := entry.Path()
			if entryPath[len(entryPath)-1] == '/' {
				subfolderIDs = append(subfolderIDs, pathToID(entryPath[0:len(entryPath)-1]))
			} else {
				pageIDs = append(pageIDs, pathToID(entryPath))
			}
		}

		var parentID interface{}
		if id != "|" {
			parentID = id[0:strings.LastIndex(id, "|")]
			if parentID == "" {
				parentID = "|"
			}
		}

		c.JSON(200, map[string]interface{}{
			"folder": map[string]interface{}{
				"id":           id,
				"pages":        pageIDs,
				"subfolders":   subfolderIDs,
				"parentFolder": parentID,
			},
		})
		return nil
	}))

	router.GET("/v2/pages/:id", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		file, err := repo.ReadFile(idToPath(id))
		if err != nil {
			return err
		}

		jsonPage, err := getPageJSON(file)
		if err != nil {
			return err
		}

		c.JSON(200, map[string]interface{}{"page": jsonPage})
		return nil
	}))

	router.DELETE("/v2/pages/:id", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		if err := repo.DeleteFile(idToPath(id)); err != nil {
			return err
		}
		c.Writer.WriteHeader(http.StatusNoContent)
		return nil
	}))

	router.POST("/v2/pages/:id/open", errorHandler(func(c *gin.Context) error {
		id := c.Params.ByName("id")

		p, err := repo.LocalPathForFile(idToPath(id))

		if err != nil {
			return err
		}

		if err := open.Run(p); err != nil {
			return err
		}

		c.Writer.WriteHeader(http.StatusNoContent)
		return nil
	}))

	router.GET("/v2/pages", errorHandler(func(c *gin.Context) error {
		var searchTerm string
		searchTermList, ok := c.Request.URL.Query()["q"]
		if ok && len(searchTermList) != 0 {
			searchTerm = searchTermList[0]
		}

		results, err := repo.SearchFiles(searchTerm)
		if err != nil {
			return err
		}

		jsonArray := []interface{}{}
		for _, file := range results {
			jsonPage, err := getPageJSON(file)
			if err != nil {
				return err
			}
			jsonArray = append(jsonArray, jsonPage)
		}

		c.JSON(200, map[string]interface{}{"pages": jsonArray})
		return nil
	}))

	return router
}

func idToPath(id string) string {
	return strings.Replace(id, "|", "/", -1)
}

func pathToID(path string) string {
	return strings.Replace(path, "/", "|", -1)
}

func getContentType(filename string) string {
	extension := filename[strings.LastIndex(filename, ".")+1:]
	switch extension {
	case "jpg":
		return "image/jpeg"
	case "png":
		return "image/png"
	case "svg":
		return "image/svg+xml"
	case "pdf":
		return "application/pdf"
	}
	return "text/plain"
}

func getPageJSON(file repository.File) (interface{}, error) {
	id := pathToID(file.Path())

	folder := id[0:strings.LastIndex(id, "|")]
	if folder == "" {
		folder = "|"
	}

	var markdownSource interface{}
	if strings.HasSuffix(id, ".md") {
		c, err := file.Reader()
		if err != nil {
			return nil, err
		}
		defer c.Close()

		markdownSourceBytes, err := ioutil.ReadAll(c)
		if err != nil {
			return nil, err
		}
		markdownSource = string(markdownSourceBytes)
	}

	return (map[string]interface{}{
		"id":             id,
		"folder":         folder,
		"modifiedAt":     file.ModTime(),
		"markdownSource": markdownSource,
	}), nil
}

func errorHandler(h func(*gin.Context) error) gin.HandlerFunc {
	return func(c *gin.Context) {
		if err := h(c); err != nil {
			if os.IsNotExist(err) {
				c.AbortWithError(404, err)
			}
			log.Println(err)
			c.AbortWithError(500, err)
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"errors"
	"warcluster/entities"
	"warcluster/server/response"
)

// The three constants bellow are used by calculateCanvasSize to determine
//the size of the area for wich information will be sent to the user.
const (
	BEST_PING  = 150
	WORST_PING = 1500
	STEPS      = 10
)

// calculateCanvasSize is used to determine how big of an area(information about an area)
// do we need to send to the user to eleminate traces of lag.
func calculateCanvasSize(position []int, resolution []int, lag int) ([]int, []int) {
	step := int(WORST_PING - BEST_PING/STEPS)
	multiply := 1.1 + float32((lag-BEST_PING)/step)*0.1
	end_resolution := []int{
		int(float32(resolution[0]) * multiply),
		int(float32(resolution[1]) * multiply),
	}

	top_left := []int{
		position[0] - int((end_resolution[0]-resolution[0])/2),
		position[1] - int((end_resolution[1]-resolution[1])/2),
	}

	bottom_right := []int{
		position[0] + resolution[0] + int((end_resolution[0]-resolution[0])/2),
		position[1] + resolution[1] + int((end_resolution[1]-resolution[1])/2),
	}
	return top_left, bottom_right
}

// scopeOfView is not finished yet but the purpose of the function is
// to call calculateCanvasSize and give the player the information
// contained in the given borders.
func scopeOfView(request *Request) error {
	res := response.NewScopeOfView()

	populate_entities := func(query string) map[string]entities.Entity {
		result := make(map[string]entities.Entity)
		entities := entities.Find(query)
		for _, entity := range entities {
			result[entity.GetKey()] = entity
		}
		return result
	}

	res.Missions = populate_entities("mission.*")
	res.Planets = populate_entities("planet.*")
	res.Suns = populate_entities("sun.*")
	return response.Send(res, request.Client.Session.Send)
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error = nil

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Invalid action!")
		}
		return nil
	}()

	source, err := entities.Get(request.StartPlanet)
	if err != nil {
		return errors.New("Start planet does not exist")
	}

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		return errors.New("End planet does not exist")
	}

	if source.(*entities.Planet).Owner != request.Client.Player.String() {
		return errors.New("This is not your home!")
	}

	if request.Type != "Attack" && request.Type != "Supply" && request.Type != "Spy" {
		return errors.New("Invalid mission type!")
	}

	mission := request.Client.Player.StartMission(source.(*entities.Planet), target.(*entities.Planet), request.Fleet, request.Type)
	go StartMissionary(mission)
	entities.Save(mission)
	entities.Save(source)

	send_mission := response.NewSendMission()
	send_mission.Mission = mission
	err = response.Send(send_mission, sessions.Broadcast)
	if err != nil {
		return err
	}

	state_change := response.NewStateChange()
	state_change.Planets = map[string]entities.Entity{
		source.GetKey(): source,
	}
	return response.Send(state_change, sessions.Broadcast)
}
<commit_msg>Split the StartMission call in server/response<commit_after>package server

import (
	"errors"
	"warcluster/entities"
	"warcluster/server/response"
)

// The three constants bellow are used by calculateCanvasSize to determine
//the size of the area for wich information will be sent to the user.
const (
	BEST_PING  = 150
	WORST_PING = 1500
	STEPS      = 10
)

// calculateCanvasSize is used to determine how big of an area(information about an area)
// do we need to send to the user to eleminate traces of lag.
func calculateCanvasSize(position []int, resolution []int, lag int) ([]int, []int) {
	step := int(WORST_PING - BEST_PING/STEPS)
	multiply := 1.1 + float32((lag-BEST_PING)/step)*0.1
	end_resolution := []int{
		int(float32(resolution[0]) * multiply),
		int(float32(resolution[1]) * multiply),
	}

	top_left := []int{
		position[0] - int((end_resolution[0]-resolution[0])/2),
		position[1] - int((end_resolution[1]-resolution[1])/2),
	}

	bottom_right := []int{
		position[0] + resolution[0] + int((end_resolution[0]-resolution[0])/2),
		position[1] + resolution[1] + int((end_resolution[1]-resolution[1])/2),
	}
	return top_left, bottom_right
}

// scopeOfView is not finished yet but the purpose of the function is
// to call calculateCanvasSize and give the player the information
// contained in the given borders.
func scopeOfView(request *Request) error {
	res := response.NewScopeOfView()

	populate_entities := func(query string) map[string]entities.Entity {
		result := make(map[string]entities.Entity)
		entities := entities.Find(query)
		for _, entity := range entities {
			result[entity.GetKey()] = entity
		}
		return result
	}

	res.Missions = populate_entities("mission.*")
	res.Planets = populate_entities("planet.*")
	res.Suns = populate_entities("sun.*")
	return response.Send(res, request.Client.Session.Send)
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error = nil

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Invalid action!")
		}
		return nil
	}()

	source, err := entities.Get(request.StartPlanet)
	if err != nil {
		return errors.New("Start planet does not exist")
	}

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		return errors.New("End planet does not exist")
	}

	if source.(*entities.Planet).Owner != request.Client.Player.String() {
		return errors.New("This is not your home!")
	}

	if request.Type != "Attack" && request.Type != "Supply" && request.Type != "Spy" {
		return errors.New("Invalid mission type!")
	}

	mission := request.Client.Player.StartMission(
		source.(*entities.Planet),
		target.(*entities.Planet),
		request.Fleet,
		request.Type,
	)
	go StartMissionary(mission)
	entities.Save(mission)
	entities.Save(source)

	send_mission := response.NewSendMission()
	send_mission.Mission = mission
	err = response.Send(send_mission, sessions.Broadcast)
	if err != nil {
		return err
	}

	state_change := response.NewStateChange()
	state_change.Planets = map[string]entities.Entity{
		source.GetKey(): source,
	}
	return response.Send(state_change, sessions.Broadcast)
}
<|endoftext|>


<commit_before>package server

import (
	"errors"

	"warcluster/entities"
	"warcluster/server/response"
)

var missionTypes = map[string]struct{}{
	"Attack": {},
	"Supply": {},
	"Spy":    {},
}

// scopeOfView is not finished yet but the purpose of the function is
// to call calculateCanvasSize and give the player the information
// contained in the given borders.
func scopeOfView(request *Request) error {
	response := response.NewScopeOfView(request.Position, request.Resolution)
	request.Client.Player.ScreenPosition = request.Position
	go entities.Save(request.Client.Player)
	clients.Send(request.Client.Player, response)
	return nil
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Invalid action!")
		}
		return nil
	}()

	sourceEntity, err := entities.Get(request.StartPlanet)
	if err != nil {
		return errors.New("Start planet does not exist")
	}
	source := sourceEntity.(*entities.Planet)

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		return errors.New("End planet does not exist")
	}

	if source.Owner != request.Client.Player.Username {
		return errors.New("The mission owner does not own the start planet.")
	}

	if _, isMissionTypeValid := missionTypes[request.Type]; !isMissionTypeValid {
		return errors.New("Invalid mission type!")
	}

	if request.StartPlanet == request.EndPlanet {
		clients.Send(request.Client.Player, response.NewComsError("Unable to target the same planet."))
		return errors.New("Start and end planet are the same. Mission cancelled.")
	}

	mission := request.Client.Player.StartMission(
		source,
		target.(*entities.Planet),
		request.Fleet,
		request.Type,
	)

	if mission.ShipCount == 0 {
		missionFailed := response.NewSendMissionFailed()
		missionFailed.Error = "Not enough pilots on source planet!"
		clients.Send(request.Client.Player, missionFailed)
	}

	entities.Save(source)
	go StartMissionary(mission)
	entities.Save(mission)

	sendMission := response.NewSendMission()
	sendMission.Mission = mission
	clients.BroadcastToAll(sendMission)

	stateChange := response.NewStateChange()
	stateChange.RawPlanets = map[string]*entities.Planet{
		source.Key(): source,
	}
	clients.BroadcastToAll(stateChange)
	return nil
}
<commit_msg>Fix starting a mission with zero ships<commit_after>package server

import (
	"errors"

	"warcluster/entities"
	"warcluster/server/response"
)

var missionTypes = map[string]struct{}{
	"Attack": {},
	"Supply": {},
	"Spy":    {},
}

// scopeOfView is not finished yet but the purpose of the function is
// to call calculateCanvasSize and give the player the information
// contained in the given borders.
func scopeOfView(request *Request) error {
	response := response.NewScopeOfView(request.Position, request.Resolution)
	request.Client.Player.ScreenPosition = request.Position
	go entities.Save(request.Client.Player)
	clients.Send(request.Client.Player, response)
	return nil
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Invalid action!")
		}
		return nil
	}()

	sourceEntity, err := entities.Get(request.StartPlanet)
	if err != nil {
		return errors.New("Start planet does not exist")
	}
	source := sourceEntity.(*entities.Planet)

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		return errors.New("End planet does not exist")
	}

	if source.Owner != request.Client.Player.Username {
		return errors.New("The mission owner does not own the start planet.")
	}

	if _, isMissionTypeValid := missionTypes[request.Type]; !isMissionTypeValid {
		return errors.New("Invalid mission type!")
	}

	if request.StartPlanet == request.EndPlanet {
		clients.Send(request.Client.Player, response.NewComsError("Unable to target the same planet."))
		return errors.New("Start and end planet are the same. Mission cancelled.")
	}

	mission := request.Client.Player.StartMission(
		source,
		target.(*entities.Planet),
		request.Fleet,
		request.Type,
	)

	if mission.ShipCount == 0 {
		missionFailed := response.NewSendMissionFailed()
		missionFailed.Error = "Not enough pilots on source planet!"
		clients.Send(request.Client.Player, missionFailed)
		return nil
	}

	entities.Save(source)
	go StartMissionary(mission)
	entities.Save(mission)

	sendMission := response.NewSendMission()
	sendMission.Mission = mission
	clients.BroadcastToAll(sendMission)

	stateChange := response.NewStateChange()
	stateChange.RawPlanets = map[string]*entities.Planet{
		source.Key(): source,
	}
	clients.BroadcastToAll(stateChange)
	return nil
}
<|endoftext|>


<commit_before>package version

// Version contains the LXD version number
var Version = "4.4"
<commit_msg>Release LXD 4.5<commit_after>package version

// Version contains the LXD version number
var Version = "4.5"
<|endoftext|>


<commit_before>package catalog

//
//import (
//	"errors"
//	"testing"
//
//	"github.com/coreos/go-semver/semver"
//	"github.com/golang/mock/gomock"
//	"github.com/stretchr/testify/assert"
//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
//
//	"github.com/coreos-inc/alm/apis/alphacatalogentry/v1alpha1"
//	csvv1alpha1 "github.com/coreos-inc/alm/apis/clusterserviceversion/v1alpha1"
//	"github.com/coreos-inc/alm/client"
//)
//
//func TestCustomCatalogStore(t *testing.T) {
//	ctrl := gomock.NewController(t)
//	mockClient := client.NewMockAlphaCatalogEntryInterface(ctrl)
//	defer ctrl.Finish()
//
//	store := CustomResourceCatalogStore{Client: mockClient}
//
//	testCSVName := "MockServiceName-v1"
//	testCSVVersion := "0.2.4+alpha"
//
//	csv := csvv1alpha1.ClusterServiceVersion{
//		TypeMeta: metav1.TypeMeta{
//			Kind:       csvv1alpha1.ClusterServiceVersionCRDName,
//			APIVersion: csvv1alpha1.GroupVersion,
//		},
//		ObjectMeta: metav1.ObjectMeta{
//			Name:      testCSVName,
//			Namespace: "alm-coreos-tests",
//		},
//		Spec: csvv1alpha1.ClusterServiceVersionSpec{
//			Version: *semver.New(testCSVVersion),
//			CustomResourceDefinitions: csvv1alpha1.CustomResourceDefinitions{
//				Owned:    []csvv1alpha1.CRDDescription{},
//				Required: []csvv1alpha1.CRDDescription{},
//			},
//		},
//	}
//	expectedEntry := v1alpha1.AlphaCatalogEntry{
//		TypeMeta: metav1.TypeMeta{
//			Kind:       v1alpha1.AlphaCatalogEntryKind,
//			APIVersion: v1alpha1.AlphaCatalogEntryCRDAPIVersion,
//		},
//		ObjectMeta: metav1.ObjectMeta{
//			Name:      testCSVName,
//			Namespace: "alm-coreos-tests",
//		},
//		Spec: &v1alpha1.AlphaCatalogEntrySpec{
//			ClusterServiceVersionSpec: csvv1alpha1.ClusterServiceVersionSpec{
//				Version: *semver.New(testCSVVersion),
//				CustomResourceDefinitions: csvv1alpha1.CustomResourceDefinitions{
//					Owned:    []csvv1alpha1.CRDDescription{},
//					Required: []csvv1alpha1.CRDDescription{},
//				},
//			},
//		},
//	}
//	returnEntry := v1alpha1.AlphaCatalogEntry{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
//	returnErr := errors.New("test error")
//	mockClient.EXPECT().UpdateEntry(&expectedEntry).Return(&returnEntry, returnErr)
//
//	actualEntry, err := store.Store(&csv)
//	assert.Equal(t, returnErr, err)
//	compareResources(t, &returnEntry, actualEntry)
//}
<commit_msg>Add entry matcher for alphacatalogentry client tests<commit_after>package catalog

import (
	"errors"
	"testing"

	"github.com/coreos/go-semver/semver"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"reflect"

	"github.com/coreos-inc/alm/apis/alphacatalogentry/v1alpha1"
	csvv1alpha1 "github.com/coreos-inc/alm/apis/clusterserviceversion/v1alpha1"
	"github.com/coreos-inc/alm/client"
)

type EntryMatcher struct{ entry v1alpha1.AlphaCatalogEntry }

func MatchesEntry(entry v1alpha1.AlphaCatalogEntry) gomock.Matcher {
	return &EntryMatcher{entry}
}

func (e *EntryMatcher) Matches(x interface{}) bool {
	entry, ok := x.(*v1alpha1.AlphaCatalogEntry)
	if !ok {
		return false
	}
	return reflect.DeepEqual(entry.Spec, e.entry.Spec)
}

func (e *EntryMatcher) String() string {
	return "matches expected entry"
}

func TestCustomCatalogStore(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockClient := client.NewMockAlphaCatalogEntryInterface(ctrl)
	defer ctrl.Finish()

	store := CustomResourceCatalogStore{Client: mockClient}

	testCSVName := "MockServiceName-v1"
	testCSVVersion := "0.2.4+alpha"

	csv := csvv1alpha1.ClusterServiceVersion{
		TypeMeta: metav1.TypeMeta{
			Kind:       csvv1alpha1.ClusterServiceVersionCRDName,
			APIVersion: csvv1alpha1.GroupVersion,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      testCSVName,
			Namespace: "alm-coreos-tests",
		},
		Spec: csvv1alpha1.ClusterServiceVersionSpec{
			Version: *semver.New(testCSVVersion),
			CustomResourceDefinitions: csvv1alpha1.CustomResourceDefinitions{
				Owned:    []csvv1alpha1.CRDDescription{},
				Required: []csvv1alpha1.CRDDescription{},
			},
		},
	}
	expectedEntry := v1alpha1.AlphaCatalogEntry{
		TypeMeta: metav1.TypeMeta{
			Kind:       v1alpha1.AlphaCatalogEntryKind,
			APIVersion: v1alpha1.AlphaCatalogEntryCRDAPIVersion,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      testCSVName,
			Namespace: "alm-coreos-tests",
		},
		Spec: &v1alpha1.AlphaCatalogEntrySpec{
			ClusterServiceVersionSpec: csvv1alpha1.ClusterServiceVersionSpec{
				Version: *semver.New(testCSVVersion),
				CustomResourceDefinitions: csvv1alpha1.CustomResourceDefinitions{
					Owned:    []csvv1alpha1.CRDDescription{},
					Required: []csvv1alpha1.CRDDescription{},
				},
			},
		},
	}
	returnEntry := v1alpha1.AlphaCatalogEntry{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
	returnErr := errors.New("test error")
	mockClient.EXPECT().UpdateEntry(MatchesEntry(expectedEntry)).Return(&returnEntry, returnErr)

	actualEntry, err := store.Store(&csv)
	assert.Equal(t, returnErr, err)
	compareResources(t, &returnEntry, actualEntry)
}
<|endoftext|>


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

package service

import (
	stderrors "errors"
	"io"
	"net/http"
	"sync"

	"github.com/tsuru/tsuru/action"
	"github.com/tsuru/tsuru/app/bind"
	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/log"
	"gopkg.in/mgo.v2/bson"
)

// createServiceInstance is an action that calls the service endpoint
// to create a service instance.
//
// The first argument in the context must be a Service.
// The second argument in the context must be a ServiceInstance.
var createServiceInstance = action.Action{
	Name: "create-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		service, ok := ctx.Params[0].(Service)
		if !ok {
			return nil, stderrors.New("First parameter must be a Service.")
		}
		endpoint, err := service.getClient("production")
		if err != nil {
			return nil, err
		}
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return nil, stderrors.New("Second parameter must be a ServiceInstance.")
		}
		user, ok := ctx.Params[2].(string)
		if !ok {
			return nil, stderrors.New("Third parameter must be a string.")
		}
		err = endpoint.Create(&instance, user)
		if err != nil {
			return nil, err
		}
		return instance, nil
	},
	Backward: func(ctx action.BWContext) {
		service, ok := ctx.Params[0].(Service)
		if !ok {
			return
		}
		endpoint, err := service.getClient("production")
		if err != nil {
			return
		}
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return
		}
		endpoint.Destroy(&instance)
	},
	MinParams: 2,
}

// insertServiceInstance is an action that inserts an instance in the database.
//
// The first argument in the context must be a Service Instance.
var insertServiceInstance = action.Action{
	Name: "insert-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return nil, stderrors.New("Second parameter must be a ServiceInstance.")
		}
		conn, err := db.Conn()
		if err != nil {
			return nil, err
		}
		defer conn.Close()
		err = conn.ServiceInstances().Insert(&instance)
		if err != nil {
			return nil, err
		}
		return nil, nil
	},
	Backward: func(ctx action.BWContext) {
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return
		}
		conn, err := db.Conn()
		if err != nil {
			return
		}
		defer conn.Close()
		conn.ServiceInstances().Remove(bson.M{"name": instance.Name})
	},
	MinParams: 2,
}

var addAppToServiceInstance = action.Action{
	Name: "add-app-to-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return nil, stderrors.New("Second parameter must be a ServiceInstance.")
		}
		conn, err := db.Conn()
		if err != nil {
			return nil, err
		}
		defer conn.Close()
		if err := conn.ServiceInstances().Find(bson.M{"name": si.Name}).One(&si); err != nil {
			return nil, err
		}
		a, ok := ctx.Params[0].(bind.App)
		if !ok {
			return nil, stderrors.New("First parameter must be a bind.App.")
		}
		if err := si.AddApp(a.GetName()); err != nil {
			return nil, &errors.HTTP{Code: http.StatusConflict, Message: "This app is already bound to this service instance."}
		}
		if err := si.update(nil); err != nil {
			return nil, err
		}
		return nil, nil
	},
	Backward: func(ctx action.BWContext) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			log.Error("Second parameter must be a ServiceInstance.")
		}
		a, ok := ctx.Params[0].(bind.App)
		if !ok {
			log.Error("First parameter must be a bind.App.")
		}
		si.RemoveApp(a.GetName())
		if err := si.update(nil); err != nil {
			log.Errorf("Could not remove app from service instance: %s", err.Error())
		}
	},
	MinParams: 2,
}

var setBindAppAction = action.Action{
	Name: "set-environ-variables-to-app",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			msg := "Second parameter must be a ServiceInstance."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			msg := "First parameter must be a bind.App."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		endpoint, err := si.Service().getClient("production")
		if err != nil {
			return nil, err
		}
		return endpoint.BindApp(&si, app)
	},
	Backward: func(ctx action.BWContext) {
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			log.Error("First parameter must be a bind.App.")
			return
		}
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			log.Error("Second parameter must be a ServiceInstance.")
			return
		}
		endpoint, err := si.Service().getClient("production")
		if err != nil {
			log.Errorf("Could not get endpoint: %s.", err.Error())
			return
		}
		endpoint.UnbindApp(&si, app)
	},
}

var setTsuruServices = action.Action{
	Name: "set-TSURU_SERVICES-env-var",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		var writer io.Writer
		if len(ctx.Params) > 2 && ctx.Params[2] != nil {
			var ok bool
			writer, ok = ctx.Params[2].(io.Writer)
			if !ok {
				msg := "Third parameter must be a io.Writer."
				log.Error(msg)
				return nil, stderrors.New(msg)
			}
		}
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			msg := "Second parameter must be a ServiceInstance."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			msg := "First parameter must be a bind.App."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		instance := bind.ServiceInstance{
			Name: si.Name,
			Envs: ctx.Previous.(map[string]string),
		}
		return instance, app.AddInstance(si.ServiceName, instance, writer)
	},
	Backward: func(ctx action.BWContext) {
		var writer io.Writer
		if len(ctx.Params) > 2 && ctx.Params[2] != nil {
			var ok bool
			writer, ok = ctx.Params[2].(io.Writer)
			if !ok {
				log.Error("Third parameter must be a io.Writer.")
				return
			}
		}
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			log.Error("Second parameter must be a ServiceInstance.")
			return
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			log.Error("First parameter must be a bind.App.")
			return
		}
		instance := ctx.FWResult.(bind.ServiceInstance)
		app.RemoveInstance(si.ServiceName, instance, writer)
	},
}

var bindUnitsToServiceInstance = action.Action{
	Name: "bind-units-to-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			msg := "Second parameter must be a ServiceInstance."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			msg := "First parameter must be a bind.App."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		units := app.GetUnits()
		if len(units) == 0 {
			return nil, nil
		}
		errChan := make(chan error, len(units)+1)
		var wg sync.WaitGroup
		wg.Add(len(units))
		for _, unit := range units {
			go func(unit bind.Unit) {
				defer wg.Done()
				err := si.BindUnit(app, unit)
				if err != nil {
					errChan <- err
					return
				}
			}(unit)
		}
		wg.Wait()
		close(errChan)
		if err, ok := <-errChan; ok {
			log.Error(err.Error())
			return nil, err
		}
		return nil, nil
	},
	Backward: func(ctx action.BWContext) {
	},
}
<commit_msg>service: ignore ErrUnitAlreadyBound on bindUnits action<commit_after>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package service

import (
	stderrors "errors"
	"io"
	"net/http"
	"sync"

	"github.com/tsuru/tsuru/action"
	"github.com/tsuru/tsuru/app/bind"
	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/log"
	"gopkg.in/mgo.v2/bson"
)

// createServiceInstance is an action that calls the service endpoint
// to create a service instance.
//
// The first argument in the context must be a Service.
// The second argument in the context must be a ServiceInstance.
var createServiceInstance = action.Action{
	Name: "create-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		service, ok := ctx.Params[0].(Service)
		if !ok {
			return nil, stderrors.New("First parameter must be a Service.")
		}
		endpoint, err := service.getClient("production")
		if err != nil {
			return nil, err
		}
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return nil, stderrors.New("Second parameter must be a ServiceInstance.")
		}
		user, ok := ctx.Params[2].(string)
		if !ok {
			return nil, stderrors.New("Third parameter must be a string.")
		}
		err = endpoint.Create(&instance, user)
		if err != nil {
			return nil, err
		}
		return instance, nil
	},
	Backward: func(ctx action.BWContext) {
		service, ok := ctx.Params[0].(Service)
		if !ok {
			return
		}
		endpoint, err := service.getClient("production")
		if err != nil {
			return
		}
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return
		}
		endpoint.Destroy(&instance)
	},
	MinParams: 2,
}

// insertServiceInstance is an action that inserts an instance in the database.
//
// The first argument in the context must be a Service Instance.
var insertServiceInstance = action.Action{
	Name: "insert-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return nil, stderrors.New("Second parameter must be a ServiceInstance.")
		}
		conn, err := db.Conn()
		if err != nil {
			return nil, err
		}
		defer conn.Close()
		err = conn.ServiceInstances().Insert(&instance)
		if err != nil {
			return nil, err
		}
		return nil, nil
	},
	Backward: func(ctx action.BWContext) {
		instance, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return
		}
		conn, err := db.Conn()
		if err != nil {
			return
		}
		defer conn.Close()
		conn.ServiceInstances().Remove(bson.M{"name": instance.Name})
	},
	MinParams: 2,
}

var addAppToServiceInstance = action.Action{
	Name: "add-app-to-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			return nil, stderrors.New("Second parameter must be a ServiceInstance.")
		}
		conn, err := db.Conn()
		if err != nil {
			return nil, err
		}
		defer conn.Close()
		if err := conn.ServiceInstances().Find(bson.M{"name": si.Name}).One(&si); err != nil {
			return nil, err
		}
		a, ok := ctx.Params[0].(bind.App)
		if !ok {
			return nil, stderrors.New("First parameter must be a bind.App.")
		}
		if err := si.AddApp(a.GetName()); err != nil {
			return nil, &errors.HTTP{Code: http.StatusConflict, Message: "This app is already bound to this service instance."}
		}
		if err := si.update(nil); err != nil {
			return nil, err
		}
		return nil, nil
	},
	Backward: func(ctx action.BWContext) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			log.Error("Second parameter must be a ServiceInstance.")
		}
		a, ok := ctx.Params[0].(bind.App)
		if !ok {
			log.Error("First parameter must be a bind.App.")
		}
		si.RemoveApp(a.GetName())
		if err := si.update(nil); err != nil {
			log.Errorf("Could not remove app from service instance: %s", err.Error())
		}
	},
	MinParams: 2,
}

var setBindAppAction = action.Action{
	Name: "set-environ-variables-to-app",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			msg := "Second parameter must be a ServiceInstance."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			msg := "First parameter must be a bind.App."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		endpoint, err := si.Service().getClient("production")
		if err != nil {
			return nil, err
		}
		return endpoint.BindApp(&si, app)
	},
	Backward: func(ctx action.BWContext) {
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			log.Error("First parameter must be a bind.App.")
			return
		}
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			log.Error("Second parameter must be a ServiceInstance.")
			return
		}
		endpoint, err := si.Service().getClient("production")
		if err != nil {
			log.Errorf("Could not get endpoint: %s.", err.Error())
			return
		}
		endpoint.UnbindApp(&si, app)
	},
}

var setTsuruServices = action.Action{
	Name: "set-TSURU_SERVICES-env-var",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		var writer io.Writer
		if len(ctx.Params) > 2 && ctx.Params[2] != nil {
			var ok bool
			writer, ok = ctx.Params[2].(io.Writer)
			if !ok {
				msg := "Third parameter must be a io.Writer."
				log.Error(msg)
				return nil, stderrors.New(msg)
			}
		}
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			msg := "Second parameter must be a ServiceInstance."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			msg := "First parameter must be a bind.App."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		instance := bind.ServiceInstance{
			Name: si.Name,
			Envs: ctx.Previous.(map[string]string),
		}
		return instance, app.AddInstance(si.ServiceName, instance, writer)
	},
	Backward: func(ctx action.BWContext) {
		var writer io.Writer
		if len(ctx.Params) > 2 && ctx.Params[2] != nil {
			var ok bool
			writer, ok = ctx.Params[2].(io.Writer)
			if !ok {
				log.Error("Third parameter must be a io.Writer.")
				return
			}
		}
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			log.Error("Second parameter must be a ServiceInstance.")
			return
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			log.Error("First parameter must be a bind.App.")
			return
		}
		instance := ctx.FWResult.(bind.ServiceInstance)
		app.RemoveInstance(si.ServiceName, instance, writer)
	},
}

var bindUnitsToServiceInstance = action.Action{
	Name: "bind-units-to-service-instance",
	Forward: func(ctx action.FWContext) (action.Result, error) {
		si, ok := ctx.Params[1].(ServiceInstance)
		if !ok {
			msg := "Second parameter must be a ServiceInstance."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		app, ok := ctx.Params[0].(bind.App)
		if !ok {
			msg := "First parameter must be a bind.App."
			log.Error(msg)
			return nil, stderrors.New(msg)
		}
		units := app.GetUnits()
		if len(units) == 0 {
			return nil, nil
		}
		errChan := make(chan error, len(units)+1)
		var wg sync.WaitGroup
		wg.Add(len(units))
		for _, unit := range units {
			go func(unit bind.Unit) {
				defer wg.Done()
				err := si.BindUnit(app, unit)
				if err != nil && err != ErrUnitAlreadyBound {
					errChan <- err
				}
			}(unit)
		}
		wg.Wait()
		close(errChan)
		if err, ok := <-errChan; ok {
			log.Error(err.Error())
			return nil, err
		}
		return nil, nil
	},
	Backward: func(ctx action.BWContext) {
	},
}
<|endoftext|>


<commit_before>package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/SudoQ/ganache/item"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"
)

type Service struct {
	URL    string        `json:"url"`
	Port   string        `json:"port"`
	Period time.Duration `json:"-"`
	Limit  uint          `json:"limit"`
	Cache  *item.Item    `json:"-"`
	logger *log.Logger   `json:"-"`
}

func New(url, port string, limit uint) *Service {
	period, err := LimitToDuration(limit)
	if err != nil {
		limit = 1
		period, _ = LimitToDuration(limit)
	}
	srv := &Service{
		URL:    url,
		Port:   port,
		Period: period,
		Limit:  limit,
		Cache:  nil,
		logger: nil,
	}
	srv.initLogger()
	return srv
}

func (this *Service) initLogger() {
	this.logger = log.New(os.Stdin, fmt.Sprintf("[ganache]"), log.Lshortfile)
}

func LimitToDuration(limit uint) (time.Duration, error) {
	if limit == 0 {
		return 0, errors.New("Division with zero")
	}
	return time.Duration(time.Duration(60/limit) * time.Minute), nil
}

func NewFromJSON(jsonBlob []byte) (*Service, error) {
	var srv Service
	err := json.Unmarshal(jsonBlob, &srv)
	if err != nil {
		return nil, err
	}
	srv.Period, err = LimitToDuration(srv.Limit)
	if err != nil {
		srv.Limit = 1
		srv.Period, _ = LimitToDuration(srv.Limit)
	}
	srv.initLogger()
	return &srv, nil
}

func NewFromFile(filename string) (*Service, error) {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return NewFromJSON(jsonBlob)
}

func (this *Service) LoadConfig(filename string) error {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = json.Unmarshal(jsonBlob, this)
	this.initLogger()
	return err
}

func (this *Service) JSON() ([]byte, error) {
	blob, err := json.Marshal(this)
	if err != nil {
		return nil, err
	}
	return blob, nil
}

func (this *Service) SaveConfig(filename string) error {
	jsonBlob, err := this.JSON()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, jsonBlob, 0644)
	return err
}

func (this *Service) Collect() {
	for {
		func(){
			now := time.Now()
			timeDelta := now.Sub(this.Cache.Timestamp)
			if this.Cache.Timestamp.Before(now) && timeDelta > this.Period {
				this.logger.Printf("GET %s\n", this.URL)

				resp, err := http.Get(this.URL)
				if err != nil {
					this.logger.Println(err)
					return
				}

				payload, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					this.logger.Println(err)
					return
				}

				// TODO Store header info?
				newItem := item.New(now, payload)
				this.Cache = newItem
				cacheFilename := "cache.json"
				err = this.Cache.WriteFile(cacheFilename)
				if err != nil {
					this.logger.Fatal(err)
				}
				this.logger.Printf("Saved cache to %s\n", cacheFilename)

			}
		}()
		select {
		case <-time.After(this.Period):
			continue
		}
	}
}

func (this *Service) LoadCache(filename string) error {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	newItem, err := item.NewFromJSON(content)
	if err != nil {
		return err
	}
	this.Cache = newItem
	return nil
}

func (this *Service) Info() string {
	return fmt.Sprintf("Ganache API caching service v0.1")
}

func (this *Service) Run() {
	err := this.LoadCache("cache.json")
	if err != nil {
		this.logger.Fatal(err)
	}
	go this.Collect()
	r := mux.NewRouter()
	r.HandleFunc("/", this.HomeHandler)
	r.HandleFunc("/info", this.InfoHandler)
	r.HandleFunc("/cache.json", this.CacheHandler)
	port := fmt.Sprintf(":%s", this.Port)
	http.ListenAndServe(port, r)
}

func (this *Service) HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(this.Cache.Payload)
}

func (this *Service) InfoHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(this.Info()))
}

func (this *Service) CacheHandler(w http.ResponseWriter, r *http.Request) {
	response, err := this.Cache.JSON()
	if err != nil {
		this.logger.Println(err)
		w.WriteHeader(404)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}
<commit_msg>Renamed LimitToDuration to LimitToPeriod<commit_after>package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/SudoQ/ganache/item"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"
)

type Service struct {
	URL    string        `json:"url"`
	Port   string        `json:"port"`
	Period time.Duration `json:"-"`
	Limit  uint          `json:"limit"`
	Cache  *item.Item    `json:"-"`
	logger *log.Logger   `json:"-"`
}

func New(url, port string, limit uint) *Service {
	period, err := LimitToPeriod(limit)
	if err != nil {
		limit = 1
		period, _ = LimitToPeriod(limit)
	}
	srv := &Service{
		URL:    url,
		Port:   port,
		Period: period,
		Limit:  limit,
		Cache:  nil,
		logger: nil,
	}
	srv.initLogger()
	return srv
}

func (this *Service) initLogger() {
	this.logger = log.New(os.Stdin, fmt.Sprintf("[ganache]"), log.Lshortfile)
}

func LimitToPeriod(limit uint) (time.Duration, error) {
	if limit == 0 {
		return 0, errors.New("Division with zero")
	}

	period := (60.0/float64(limit))*60
	return (time.Duration(period) * time.Second), nil
}

func NewFromJSON(jsonBlob []byte) (*Service, error) {
	var srv Service
	err := json.Unmarshal(jsonBlob, &srv)
	if err != nil {
		return nil, err
	}
	srv.Period, err = LimitToPeriod(srv.Limit)
	if err != nil {
		srv.Limit = 1
		srv.Period, _ = LimitToPeriod(srv.Limit)
	}
	srv.initLogger()
	return &srv, nil
}

func NewFromFile(filename string) (*Service, error) {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return NewFromJSON(jsonBlob)
}

func (this *Service) LoadConfig(filename string) error {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = json.Unmarshal(jsonBlob, this)
	this.initLogger()
	return err
}

func (this *Service) JSON() ([]byte, error) {
	blob, err := json.Marshal(this)
	if err != nil {
		return nil, err
	}
	return blob, nil
}

func (this *Service) SaveConfig(filename string) error {
	jsonBlob, err := this.JSON()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, jsonBlob, 0644)
	return err
}

func (this *Service) Collect() {
	for {
		func(){
			now := time.Now()
			timeDelta := now.Sub(this.Cache.Timestamp)
			if this.Cache.Timestamp.Before(now) && timeDelta > this.Period {
				this.logger.Printf("GET %s\n", this.URL)

				resp, err := http.Get(this.URL)
				if err != nil {
					this.logger.Println(err)
					return
				}

				payload, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					this.logger.Println(err)
					return
				}

				// TODO Store header info?
				newItem := item.New(now, payload)
				this.Cache = newItem
				cacheFilename := "cache.json"
				err = this.Cache.WriteFile(cacheFilename)
				if err != nil {
					this.logger.Fatal(err)
				}
				this.logger.Printf("Saved cache to %s\n", cacheFilename)

			}
		}()
		select {
		case <-time.After(this.Period):
			continue
		}
	}
}

func (this *Service) LoadCache(filename string) error {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	newItem, err := item.NewFromJSON(content)
	if err != nil {
		return err
	}
	this.Cache = newItem
	return nil
}

func (this *Service) Info() string {
	return fmt.Sprintf("Ganache API caching service v0.1")
}

func (this *Service) Run() {
	err := this.LoadCache("cache.json")
	if err != nil {
		this.logger.Fatal(err)
	}
	go this.Collect()
	r := mux.NewRouter()
	r.HandleFunc("/", this.HomeHandler)
	r.HandleFunc("/info", this.InfoHandler)
	r.HandleFunc("/cache.json", this.CacheHandler)
	port := fmt.Sprintf(":%s", this.Port)
	http.ListenAndServe(port, r)
}

func (this *Service) HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(this.Cache.Payload)
}

func (this *Service) InfoHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)
	w.Write([]byte(this.Info()))
}

func (this *Service) CacheHandler(w http.ResponseWriter, r *http.Request) {
	response, err := this.Cache.JSON()
	if err != nil {
		this.logger.Println(err)
		w.WriteHeader(404)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}
<|endoftext|>


<commit_before>package router

import (
	"errors"
	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/cosminrentea/gobbler/protocol"
	"github.com/cosminrentea/gobbler/server/store"
)

var (
	errEmptyQueue = errors.New("Empty queue")
	errTimeout    = errors.New("Channel sending timeout")

	ErrMissingFetchRequest = errors.New("Missing FetchRequest configuration.")
)

// Route represents a topic for subscription that has a channel to receive messages.
type Route struct {
	RouteConfig

	messagesC chan *protocol.Message

	// queue that will store the messages in correct order.
	// The queue can have a settable size;
	// if it reaches the capacity the route is closed.
	queue *queue

	closeC chan struct{}

	// Indicates if the consumer go routine is running
	consuming bool
	invalid   bool
	mu        sync.RWMutex

	logger *log.Entry
}

// NewRoute creates a new route pointer
func NewRoute(config RouteConfig) *Route {
	route := &Route{
		RouteConfig: config,

		queue:     newQueue(config.QueueSize),
		messagesC: make(chan *protocol.Message, config.ChannelSize),
		closeC:    make(chan struct{}),

		logger: logger.WithFields(log.Fields{"path": config.Path, "params": config.RouteParams}),
	}

	return route
}

// Key returns a string that uniquely identifies the route
// by concatenating the route Path and the route params
// Example:
//  /topic user_id:user1 application_id:app1
func (r *Route) Key() string {
	return strings.Join([]string{
		string(r.Path),
		r.RouteParams.Key(),
	}, " ")
}

func (r *Route) String() string {
	return fmt.Sprintf("Path: %s , Params: %s", r.Path, r.RouteParams)
}

// Deliver takes a messages and adds it to the queue to be delivered into the channel
// isFromStore boolean specifies if the messages are being fetched or are from the router
// In case they are fetched from the store the route won't close if it's full
func (r *Route) Deliver(msg *protocol.Message, isFromStore bool) error {
	loggerMessage := r.logger.WithField("message", msg)

	if r.isInvalid() {
		loggerMessage.Error("Cannot deliver because route is invalid")
		mTotalDeliverMessageErrors.Add(1)
		return ErrInvalidRoute
	}

	if !r.messageFilter(msg) {
		loggerMessage.Debug("Message filter didn't match route")
		mTotalNotMatchedByFilters.Add(1)
		return nil
	}
	// not an infinite queue
	if r.QueueSize >= 0 {
		// if size is zero the sending is direct
		if r.QueueSize == 0 {
			return r.sendDirect(msg, isFromStore)
		} else if r.queue.size() >= r.QueueSize {
			loggerMessage.Error("Closing route because queue is full")
			r.Close()
			mTotalDeliverMessageErrors.Add(1)
			return ErrQueueFull
		}
	}

	r.queue.push(msg)
	loggerMessage.WithField("queue_size", r.queue.size()).Debug("Deliver")

	r.consume()
	return nil
}

// MessagesChannel returns the route channel to send or receive messages.
func (r *Route) MessagesChannel() <-chan *protocol.Message {
	return r.messagesC
}

// Provide accepts a router to use for fetching/subscribing and a boolean
// indicating if it should close the route after fetching without subscribing
// The method is blocking until fetch is finished or route is subscribed
func (r *Route) Provide(router Router, subscribe bool) error {
	if r.FetchRequest != nil {
		err := r.handleFetch(router)
		if err != nil {
			return err
		}
	} else if !subscribe {
		return ErrMissingFetchRequest
	}
	if !subscribe {
		return nil
	}
	return r.handleSubscribe(router)
}

func (r *Route) handleFetch(router Router) error {
	if r.isInvalid() {
		return ErrInvalidRoute
	}

	r.FetchRequest.Partition = r.Path.Partition()
	ms, err := router.MessageStore()
	if err != nil {
		return err
	}

	var (
		lastID   uint64
		received int
	)

REFETCH:
	// check if we need to continue fetching
	maxID, err := ms.MaxMessageID(r.FetchRequest.Partition)
	if err != nil {
		return err
	}

	if r.FetchRequest.StartID > maxID && r.FetchRequest.Direction == store.DirectionForward {
		return nil
	}

	if received >= r.FetchRequest.Count || lastID >= maxID ||
		(r.FetchRequest.EndID > 0 && r.FetchRequest.EndID <= lastID) {
		return nil
	}
	r.FetchRequest.Init()

	if err := router.Fetch(r.FetchRequest); err != nil {
		return err
	}
	count := r.FetchRequest.Ready()
	r.logger.WithField("count", count).Debug("Receiving messages")

	for {
		select {
		case fetchedMessage, open := <-r.FetchRequest.Messages():
			if !open {
				r.logger.Debug("Fetch channel closed.")
				goto REFETCH
			}

			r.logger.WithField("fetchedMessageID", fetchedMessage.ID).Debug("Fetched message")
			message, err := protocol.ParseMessage(fetchedMessage.Message)
			if err != nil {
				return err
			}

			r.logger.WithField("messageID", message.ID).Debug("Sending fetched message in channel")
			if err := r.Deliver(message, true); err != nil {
				return err
			}
			lastID = message.ID
			received++
		case err := <-r.FetchRequest.Errors():
			return err
		case <-router.Done():
			r.logger.Debug("Stopping fetch because the router is shutting down")
			return nil
		}
	}
}

func (r *Route) handleSubscribe(router Router) error {
	_, err := router.Subscribe(r)
	return err
}

// Close closes the route channel.
func (r *Route) Close() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.logger.Debug("Closing route")

	// route already closed
	if r.invalid {
		return ErrInvalidRoute
	}

	r.invalid = true
	close(r.messagesC)
	close(r.closeC)

	return ErrInvalidRoute
}

// Equal will check if the route path is matched and all the parameters or just a
// subset of specific parameters between the routes
func (r *Route) Equal(other *Route, keys ...string) bool {
	return r.RouteConfig.Equal(other.RouteConfig, keys...)
}

// IsInvalid returns true if the route is invalid, has been closed previously
func (r *Route) isInvalid() bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.invalid
}

func (r *Route) setInvalid(invalid bool) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.invalid = invalid
}

func (r *Route) isConsuming() bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.consuming
}

func (r *Route) setConsuming(consuming bool) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.consuming = consuming
}

// consume starts a goroutine to consume the queue and pass the messages to route
// channel. Stops if there are no items in the queue.
func (r *Route) consume() {
	if r.isConsuming() {
		return
	}
	r.setConsuming(true)

	r.logger.Debug("Consuming route queue")
	go func() {
		defer r.setConsuming(false)

		var (
			msg *protocol.Message
			err error
		)

		for {
			if r.isInvalid() {
				r.logger.Debug("Stopping to consume because route is invalid.")
				mTotalDeliverMessageErrors.Add(1)
				return
			}

			msg, err = r.queue.poll()

			if err != nil {
				if err == errEmptyQueue {
					r.logger.Debug("Empty queue")
					return
				}
				r.logger.WithField("error", err).Error("Error fetching a message from queue")
				continue
			}

			if err = r.send(msg); err != nil {
				r.logger.WithField("message", msg).Error("Error sending message through route")
				if err == errTimeout || err == ErrInvalidRoute {
					// channel been closed, ending the consumer
					return
				}
			}
			// remove the first item from the queue
			r.queue.remove()
		}
	}()
	runtime.Gosched()
}

// send message through the channel
func (r *Route) send(msg *protocol.Message) error {
	defer r.invalidRecover()

	r.logger.WithField("message", msg).Debug("Sending message through route channel")

	// no timeout, means we don't close the channel
	if r.Timeout == -1 {
		r.messagesC <- msg
		r.logger.WithField("size", len(r.messagesC)).Debug("Channel size")
		return nil
	}

	select {
	case r.messagesC <- msg:
		return nil
	case <-r.closeC:
		return ErrInvalidRoute
	case <-time.After(r.Timeout):
		r.logger.Debug("Closing route because of timeout")
		r.Close()
		return errTimeout
	}
}

// invalidRecover is used to recover in case we end up sending on a closed channel
func (r *Route) invalidRecover() error {
	if rc := recover(); rc != nil && r.isInvalid() {
		r.logger.WithField("error", rc).Debug("Recovered closed route")
		return ErrInvalidRoute
	}
	return nil
}

// sendDirect sends the message directly in the channel
func (r *Route) sendDirect(msg *protocol.Message, store bool) error {
	if store {
		r.messagesC <- msg
		return nil
	}

	select {
	case r.messagesC <- msg:
		return nil
	default:
		r.logger.Debug("Closing route because of full channel")
		r.Close()
		return ErrChannelFull
	}
}
<commit_msg>Update logs for route consuming<commit_after>package router

import (
	"errors"
	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/cosminrentea/gobbler/protocol"
	"github.com/cosminrentea/gobbler/server/store"
)

var (
	errEmptyQueue = errors.New("Empty queue")
	errTimeout    = errors.New("Channel sending timeout")

	ErrMissingFetchRequest = errors.New("Missing FetchRequest configuration.")
)

// Route represents a topic for subscription that has a channel to receive messages.
type Route struct {
	RouteConfig

	messagesC chan *protocol.Message

	// queue that will store the messages in correct order.
	// The queue can have a settable size;
	// if it reaches the capacity the route is closed.
	queue *queue

	closeC chan struct{}

	// Indicates if the consumer go routine is running
	consuming bool
	invalid   bool
	mu        sync.RWMutex

	logger *log.Entry
}

// NewRoute creates a new route pointer
func NewRoute(config RouteConfig) *Route {
	route := &Route{
		RouteConfig: config,

		queue:     newQueue(config.QueueSize),
		messagesC: make(chan *protocol.Message, config.ChannelSize),
		closeC:    make(chan struct{}),

		logger: logger.WithFields(log.Fields{"path": config.Path, "params": config.RouteParams}),
	}

	return route
}

// Key returns a string that uniquely identifies the route
// by concatenating the route Path and the route params
// Example:
//  /topic user_id:user1 application_id:app1
func (r *Route) Key() string {
	return strings.Join([]string{
		string(r.Path),
		r.RouteParams.Key(),
	}, " ")
}

func (r *Route) String() string {
	return fmt.Sprintf("Path: %s , Params: %s", r.Path, r.RouteParams)
}

// Deliver takes a messages and adds it to the queue to be delivered into the channel
// isFromStore boolean specifies if the messages are being fetched or are from the router
// In case they are fetched from the store the route won't close if it's full
func (r *Route) Deliver(msg *protocol.Message, isFromStore bool) error {
	loggerMessage := r.logger.WithField("message", msg)

	if r.isInvalid() {
		loggerMessage.Error("Cannot deliver because route is invalid")
		mTotalDeliverMessageErrors.Add(1)
		return ErrInvalidRoute
	}

	if !r.messageFilter(msg) {
		loggerMessage.Debug("Message filter didn't match route")
		mTotalNotMatchedByFilters.Add(1)
		return nil
	}
	// not an infinite queue
	if r.QueueSize >= 0 {
		// if size is zero the sending is direct
		if r.QueueSize == 0 {
			return r.sendDirect(msg, isFromStore)
		} else if r.queue.size() >= r.QueueSize {
			loggerMessage.Error("Closing route because queue is full")
			r.Close()
			mTotalDeliverMessageErrors.Add(1)
			return ErrQueueFull
		}
	}

	r.queue.push(msg)
	loggerMessage.WithField("queue_size", r.queue.size()).Debug("Deliver")

	r.consume()
	return nil
}

// MessagesChannel returns the route channel to send or receive messages.
func (r *Route) MessagesChannel() <-chan *protocol.Message {
	return r.messagesC
}

// Provide accepts a router to use for fetching/subscribing and a boolean
// indicating if it should close the route after fetching without subscribing
// The method is blocking until fetch is finished or route is subscribed
func (r *Route) Provide(router Router, subscribe bool) error {
	if r.FetchRequest != nil {
		err := r.handleFetch(router)
		if err != nil {
			return err
		}
	} else if !subscribe {
		return ErrMissingFetchRequest
	}
	if !subscribe {
		return nil
	}
	return r.handleSubscribe(router)
}

func (r *Route) handleFetch(router Router) error {
	if r.isInvalid() {
		return ErrInvalidRoute
	}

	r.FetchRequest.Partition = r.Path.Partition()
	ms, err := router.MessageStore()
	if err != nil {
		return err
	}

	var (
		lastID   uint64
		received int
	)

REFETCH:
	// check if we need to continue fetching
	maxID, err := ms.MaxMessageID(r.FetchRequest.Partition)
	if err != nil {
		return err
	}

	if r.FetchRequest.StartID > maxID && r.FetchRequest.Direction == store.DirectionForward {
		return nil
	}

	if received >= r.FetchRequest.Count || lastID >= maxID ||
		(r.FetchRequest.EndID > 0 && r.FetchRequest.EndID <= lastID) {
		return nil
	}
	r.FetchRequest.Init()

	if err := router.Fetch(r.FetchRequest); err != nil {
		return err
	}
	count := r.FetchRequest.Ready()
	r.logger.WithField("count", count).Debug("Receiving messages")

	for {
		select {
		case fetchedMessage, open := <-r.FetchRequest.Messages():
			if !open {
				r.logger.Debug("Fetch channel closed.")
				goto REFETCH
			}

			r.logger.WithField("fetchedMessageID", fetchedMessage.ID).Debug("Fetched message")
			message, err := protocol.ParseMessage(fetchedMessage.Message)
			if err != nil {
				return err
			}

			r.logger.WithField("messageID", message.ID).Debug("Sending fetched message in channel")
			if err := r.Deliver(message, true); err != nil {
				return err
			}
			lastID = message.ID
			received++
		case err := <-r.FetchRequest.Errors():
			return err
		case <-router.Done():
			r.logger.Debug("Stopping fetch because the router is shutting down")
			return nil
		}
	}
}

func (r *Route) handleSubscribe(router Router) error {
	_, err := router.Subscribe(r)
	return err
}

// Close closes the route channel.
func (r *Route) Close() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.logger.Debug("Closing route")

	// route already closed
	if r.invalid {
		return ErrInvalidRoute
	}

	r.invalid = true
	close(r.messagesC)
	close(r.closeC)

	return ErrInvalidRoute
}

// Equal will check if the route path is matched and all the parameters or just a
// subset of specific parameters between the routes
func (r *Route) Equal(other *Route, keys ...string) bool {
	return r.RouteConfig.Equal(other.RouteConfig, keys...)
}

// IsInvalid returns true if the route is invalid, has been closed previously
func (r *Route) isInvalid() bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.invalid
}

func (r *Route) setInvalid(invalid bool) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.invalid = invalid
}

func (r *Route) isConsuming() bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.consuming
}

func (r *Route) setConsuming(consuming bool) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.consuming = consuming
}

// consume starts a goroutine to consume the queue and pass the messages to route
// channel. Stops if there are no items in the queue.
func (r *Route) consume() {
	if r.isConsuming() {
		return
	}
	r.setConsuming(true)

	r.logger.Debug("Consuming route queue")
	go func() {
		defer r.setConsuming(false)

		var (
			msg *protocol.Message
			err error
		)

		for {
			r.logger.WithField("size", r.queue.size()).Debug("Queue size")

			if r.isInvalid() {
				r.logger.Debug("Stopping to consume because route is invalid.")
				mTotalDeliverMessageErrors.Add(1)
				return
			}

			msg, err = r.queue.poll()

			if err != nil {
				if err == errEmptyQueue {
					r.logger.Debug("Empty queue")
					return
				}
				r.logger.WithField("error", err).Error("Error fetching a message from queue")
				continue
			}

			if err = r.send(msg); err != nil {
				r.logger.WithFields(log.Fields{
					"message": msg,
					"error":   err.Error(),
				}).Error("Error sending message through route")
				if err == errTimeout || err == ErrInvalidRoute {
					// channel been closed, ending the consumer
					return
				}
			}
			// remove the first item from the queue
			r.queue.remove()
		}
	}()
	runtime.Gosched()
}

// send message through the channel
func (r *Route) send(msg *protocol.Message) error {
	defer r.invalidRecover()

	r.logger.WithField("message", msg).Debug("Sending message through route channel")

	// no timeout, means we don't close the channel
	if r.Timeout == -1 {
		r.messagesC <- msg
		r.logger.WithField("size", len(r.messagesC)).Debug("Channel size")
		return nil
	}

	select {
	case r.messagesC <- msg:
		return nil
	case <-r.closeC:
		return ErrInvalidRoute
	case <-time.After(r.Timeout):
		r.logger.Debug("Closing route because of timeout")
		r.Close()
		return errTimeout
	}
}

// invalidRecover is used to recover in case we end up sending on a closed channel
func (r *Route) invalidRecover() error {
	if rc := recover(); rc != nil && r.isInvalid() {
		r.logger.WithField("error", rc).Debug("Recovered closed route")
		return ErrInvalidRoute
	}
	return nil
}

// sendDirect sends the message directly in the channel
func (r *Route) sendDirect(msg *protocol.Message, store bool) error {
	if store {
		r.messagesC <- msg
		return nil
	}

	select {
	case r.messagesC <- msg:
		return nil
	default:
		r.logger.Debug("Closing route because of full channel")
		r.Close()
		return ErrChannelFull
	}
}
<|endoftext|>


<commit_before><commit_msg>Correctly detect not exist error<commit_after><|endoftext|>