<commit_before>package bot

import (
	"regexp"
	"strings"
	"sync"

	log "github.com/sirupsen/logrus"

	"github.com/nlopes/slack"
	"github.com/pastjean/scrumpolice/scrum"
)

var (
	OutOfOfficeRegex, _ = regexp.Compile("^@?(.+) is out of office$")
)

type (
	Bot struct {
		slackBotRTM *slack.RTM
		slackBotAPI *slack.Client

		userContextsMutex sync.Mutex
		userContexts      map[string]BotContextHandler

		scrum scrum.Service

		name    string
		iconURL string
		id      string

		logger *log.Logger
	}
)

func New(slackApiClient *slack.Client, logger *log.Logger, scrum scrum.Service) *Bot {
	slackBotRTM := slackApiClient.NewRTM()
	go slackBotRTM.ManageConnection()

	return &Bot{
		slackBotAPI:  slackApiClient,
		slackBotRTM:  slackBotRTM,
		logger:       logger,
		userContexts: map[string]BotContextHandler{},
		iconURL:      "http://i.imgur.com/dzZvzXm.jpg",
		scrum:        scrum,
	}
}

func (b *Bot) Run() {
	go func() {
		for msg := range b.slackBotRTM.IncomingEvents {
			switch evt := msg.Data.(type) {
			case *slack.MessageEvent:
				go b.handleMessage(evt)
			case *slack.InvalidAuthEvent:
				go b.handleInvalidAuth(evt)
			case *slack.ConnectedEvent:
				go b.handleConnected(evt)
			}
		}
	}()

	select {}
}

func (b *Bot) handleMessage(event *slack.MessageEvent) {
	if event.BotID != "" {
		// Ignore the messages coming from other bots
		return
	}

	isIM := false
	switch event.Channel[0] {
	case 'D':
		isIM = true
		// IM
	case 'G':
		// GROUP
	default:
		// ALL OTHER CHANNEL TYPES
	}

	eventText := strings.ToLower(event.Text)

	if !b.HandleScrumMessage(event) {
		return
	}

	// HANDLE GLOBAL PUBLIC COMMANDS HERE
	if strings.Contains(eventText, ":wave:") {
		b.reactToEvent(event, "wave")
		return
	}

	if !isIM && !b.adressedToMe(eventText) {
		return
	}

	// FROM HERE All Commands need to be adressed to me or handled in private conversations
	adressedToMe := b.adressedToMe(eventText)
	if !isIM && adressedToMe {
		eventText = b.trimBotNameInMessage(eventText)
	}

	// From here on i only care of messages that were clearly adressed to me so i'll just get out
	if !adressedToMe && !isIM {
		return
	}

	// Handle commands adressed to me (can be public or private)
	if eventText == "source code" {
		b.sourceCode(event)
		return
	}

	if eventText == "help" {
		b.help(event)
		return
	}

	if eventText == "tutorial" {
		go b.tutorial(event)
		return
	}

	if eventText == "out of office" {
		b.outOfOffice(event, event.User, true)
		return
	}

	if OutOfOfficeRegex.MatchString(eventText) {
		b.outOfOffice(event, strings.Split(strings.Trim(eventText, " "), " ")[0], false)
		return
	}

	if eventText == "i'm back" || eventText == "i am back" {
		b.backInOffice(event)
		return
	}

	// Unrecognized message so let's help the user
	b.unrecognizedMessage(event)
	return
}

func (b *Bot) adressedToMe(msg string) bool {
	return strings.HasPrefix(msg, strings.ToLower("<@"+b.id+">")) ||
		strings.HasPrefix(msg, strings.ToLower(b.name))
}

func (b *Bot) trimBotNameInMessage(msg string) string {
	msg = strings.Replace(msg, strings.ToLower("<@"+b.id+">"), "", 1)
	msg = strings.Replace(msg, strings.ToLower(b.name), "", 1)
	msg = strings.Trim(msg, " :\n")

	return msg
}

func (b *Bot) handleInvalidAuth(event *slack.InvalidAuthEvent) {
	b.logger.WithFields(log.Fields{
		"event": event,
	}).Fatalln("Invalid authentication credentials")
}

func (b *Bot) handleConnected(event *slack.ConnectedEvent) {
	b.id = event.Info.User.ID
	b.name = event.Info.User.Name
}

func (b *Bot) reactToEvent(event *slack.MessageEvent, reaction string) {
	item := slack.ItemRef{
		Channel:   event.Channel,
		Timestamp: event.Timestamp,
	}
	err := b.slackBotAPI.AddReaction(reaction, item)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to add reaction to slack.")
		return
	}
}

func (b *Bot) sourceCode(event *slack.MessageEvent) {
	params := slack.PostMessageParameters{AsUser: true}
	_, _, err := b.slackBotAPI.PostMessage(event.Channel, "My source code is here <https://github.com/pastjean/scrumpolice>", params)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to post message to slack.")
		return
	}
}

func (b *Bot) help(event *slack.MessageEvent) {
	message := slack.Attachment{
		MarkdownIn: []string{"text"},
		Text: "- `source code`: location of my source code\n" +
			"- `help`: well, this command\n" +
			"- `tutorial`: explains how the scrum police works. Try it!\n" +
			"- `start`: starts a scrum for a team and a specific set of questions, defaults to your only team if you got only one, and only questions set if there's only one on the team you chose\n" +
			"- `restart`: restart your last done scrum, if it wasn't posted\n" +
			"- `out of office`: mark current user as out of office (until `i'm back` is used)\n" +
			"- `[user] is out of office`: mark the specified user as out of office (until he or she uses `i'm back`)\n" +
			"- `i am back` or `i'm back`: mark current user as in office. MacOS smart quote can screw up with the `i'm back` command.",
	}

	params := slack.PostMessageParameters{AsUser: true}
	params.Attachments = []slack.Attachment{message}

	_, _, err := b.slackBotAPI.PostMessage(event.Channel, "Here's a list of supported commands", params)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to post message to slack.")
		return
	}
}

// This method sleeps to give a better feeling to the user. It should be use in a sub-routine.
func (b *Bot) tutorial(event *slack.MessageEvent) {
	params := slack.PostMessageParameters{AsUser: true}

	b.slackBotAPI.PostMessage(event.Channel,
		"*Hi there* :wave: You're new, aren't you? You want to know how I do thing? Here :golang:es!\n"+
			"When you want to start a scrum report, just tell me `start` in a direct message :flag-dm:. _If you are part of more than one team, specify the team (I will ask you if you don't)_\n"+
			"Then, I will ask you a couple of questions, and wait for your answers. Once you anwsered all the questions, you're done :white_check_mark:.\n"+
			"I take care of the rest! :cop:\n"+
			"When it's time :clock10:, I will post the scrum report for you and your friends in your team's channel :raised_hands:\n"+
			"All you have to do now is read the report :book: (when you have the time, I don't want to rush you :scream:)\n"+
			"That's all. Enjoy :beers:.",
		params)
}

func (b *Bot) outOfOffice(event *slack.MessageEvent, userId string, resolveUser bool) {
	params := slack.PostMessageParameters{AsUser: true}
	username := strings.TrimLeft(userId, "@")

	if resolveUser {
		user, err := b.slackBotAPI.GetUserInfo(username)
		if err != nil {
			b.logSlackRelatedError(event, err, "Fail to get user information.")
			b.slackBotAPI.PostMessage(event.Channel, "Hmmmm, I couldn't find you. Try again!", params)
			return
		}
		username = user.Name
	}

	teams := b.scrum.GetTeamsForUser(username)

	for _, team := range teams {
		b.scrum.AddToOutOfOffice(team, username)
	}
	if event.User == userId {
		b.slackBotAPI.PostMessage(event.Channel, "I've marked you out of office in all your teams", params)
		log.WithFields(log.Fields{
			"user":   username,
			"doneBy": username,
		}).Info("User was marked out of office.")
	} else {
		b.slackBotAPI.PostMessage(event.Channel, "I've marked @"+username+" out of office in all of his teams", params)

		user, err := b.slackBotAPI.GetUserInfo(event.User)
		if err != nil {
			b.logSlackRelatedError(event, err, "Fail to get user information.")
			return
		}
		b.slackBotAPI.PostMessage("@"+username, "You've been marked out of office by @"+user.Name+".", params)
		log.WithFields(log.Fields{
			"user":   userId,
			"doneBy": user.Name,
		}).Info("User was marked out of office.")
	}
}

func (b *Bot) backInOffice(event *slack.MessageEvent) {
	params := slack.PostMessageParameters{AsUser: true}
	user, err := b.slackBotAPI.GetUserInfo(event.User)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to get user information.")
		b.slackBotAPI.PostMessage(event.Channel, "Hmmmm, I couldn't find you. Try again!", params)
		return
	}
	username := user.Name

	teams := b.scrum.GetTeamsForUser(username)

	for _, team := range teams {
		b.scrum.RemoveFromOutOfOffice(team, username)
	}
	b.slackBotAPI.PostMessage(event.Channel, "I've marked you in office in all your teams. Welcome back!", params)
	log.WithFields(log.Fields{
		"user": username,
	}).Info("User was marked in office.")
}

func (b *Bot) unrecognizedMessage(event *slack.MessageEvent) {
	log.WithFields(log.Fields{
		"text": event.Text,
		"user": event.Username,
	}).Info("Received unrecognized message.")
	params := slack.PostMessageParameters{AsUser: true}

	_, _, err := b.slackBotAPI.PostMessage(event.Channel, "I don't understand what you're trying to tell me, try `help`", params)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to post message to slack.")
		return
	}
}

func (b *Bot) canQuitBotContextHandlerFunc(handler func(event *slack.MessageEvent) bool) BotContextHandler {
	return b.canQuitBotContext(BotContextHandlerFunc(handler))
}

func (b *Bot) canQuitBotContext(handler BotContextHandler) BotContextHandler {
	return BotContextHandlerFunc(func(event *slack.MessageEvent) bool {
		if event.Text == "quit" {
			b.slackBotAPI.PostMessage(event.Channel, "Action is canceled, if you wanna do anything else, just poke me, `help` is always available! :wave:", slack.PostMessageParameters{AsUser: true})
			delete(b.userContexts, event.User)
			return false
		}

		return handler.HandleMessage(event)
	})
}

func (b *Bot) setUserContext(user string, context BotContextHandler) {
	b.userContextsMutex.Lock()
	b.userContexts[user] = context
	b.userContextsMutex.Unlock()
}

func (b *Bot) unsetUserContext(user string) {
	b.userContextsMutex.Lock()
	delete(b.userContexts, user)
	b.userContextsMutex.Unlock()
}

func (b *Bot) logSlackRelatedError(event *slack.MessageEvent, err error, logMessage string) {
	b.logger.WithFields(log.Fields{
		"text":  event.Text,
		"user":  event.Username,
		"error": err,
	}).Error(logMessage)
}
<commit_msg>correctly i said<commit_after>package bot

import (
	"regexp"
	"strings"
	"sync"

	log "github.com/sirupsen/logrus"

	"github.com/nlopes/slack"
	"github.com/pastjean/scrumpolice/scrum"
)

var (
	OutOfOfficeRegex, _ = regexp.Compile("^(.+) is out of office$")
)

type (
	Bot struct {
		slackBotRTM *slack.RTM
		slackBotAPI *slack.Client

		userContextsMutex sync.Mutex
		userContexts      map[string]BotContextHandler

		scrum scrum.Service

		name    string
		iconURL string
		id      string

		logger *log.Logger
	}
)

func New(slackApiClient *slack.Client, logger *log.Logger, scrum scrum.Service) *Bot {
	slackBotRTM := slackApiClient.NewRTM()
	go slackBotRTM.ManageConnection()

	return &Bot{
		slackBotAPI:  slackApiClient,
		slackBotRTM:  slackBotRTM,
		logger:       logger,
		userContexts: map[string]BotContextHandler{},
		iconURL:      "http://i.imgur.com/dzZvzXm.jpg",
		scrum:        scrum,
	}
}

func (b *Bot) Run() {
	go func() {
		for msg := range b.slackBotRTM.IncomingEvents {
			switch evt := msg.Data.(type) {
			case *slack.MessageEvent:
				go b.handleMessage(evt)
			case *slack.InvalidAuthEvent:
				go b.handleInvalidAuth(evt)
			case *slack.ConnectedEvent:
				go b.handleConnected(evt)
			}
		}
	}()

	select {}
}

func (b *Bot) handleMessage(event *slack.MessageEvent) {
	if event.BotID != "" {
		// Ignore the messages coming from other bots
		return
	}

	isIM := false
	switch event.Channel[0] {
	case 'D':
		isIM = true
		// IM
	case 'G':
		// GROUP
	default:
		// ALL OTHER CHANNEL TYPES
	}

	eventText := strings.ToLower(event.Text)

	if !b.HandleScrumMessage(event) {
		return
	}

	// HANDLE GLOBAL PUBLIC COMMANDS HERE
	if strings.Contains(eventText, ":wave:") {
		b.reactToEvent(event, "wave")
		return
	}

	if !isIM && !b.adressedToMe(eventText) {
		return
	}

	// FROM HERE All Commands need to be adressed to me or handled in private conversations
	adressedToMe := b.adressedToMe(eventText)
	if !isIM && adressedToMe {
		eventText = b.trimBotNameInMessage(eventText)
	}

	// From here on i only care of messages that were clearly adressed to me so i'll just get out
	if !adressedToMe && !isIM {
		return
	}

	// Handle commands adressed to me (can be public or private)
	if eventText == "source code" {
		b.sourceCode(event)
		return
	}

	if eventText == "help" {
		b.help(event)
		return
	}

	if eventText == "tutorial" {
		go b.tutorial(event)
		return
	}

	if eventText == "out of office" {
		b.outOfOffice(event, event.User, true)
		return
	}

	if OutOfOfficeRegex.MatchString(eventText) {
		b.outOfOffice(event, strings.Split(strings.Trim(eventText, " "), " ")[0], false)
		return
	}

	if eventText == "i'm back" || eventText == "i am back" {
		b.backInOffice(event)
		return
	}

	// Unrecognized message so let's help the user
	b.unrecognizedMessage(event)
	return
}

func (b *Bot) adressedToMe(msg string) bool {
	return strings.HasPrefix(msg, strings.ToLower("<@"+b.id+">")) ||
		strings.HasPrefix(msg, strings.ToLower(b.name))
}

func (b *Bot) trimBotNameInMessage(msg string) string {
	msg = strings.Replace(msg, strings.ToLower("<@"+b.id+">"), "", 1)
	msg = strings.Replace(msg, strings.ToLower(b.name), "", 1)
	msg = strings.Trim(msg, " :\n")

	return msg
}

func (b *Bot) handleInvalidAuth(event *slack.InvalidAuthEvent) {
	b.logger.WithFields(log.Fields{
		"event": event,
	}).Fatalln("Invalid authentication credentials")
}

func (b *Bot) handleConnected(event *slack.ConnectedEvent) {
	b.id = event.Info.User.ID
	b.name = event.Info.User.Name
}

func (b *Bot) reactToEvent(event *slack.MessageEvent, reaction string) {
	item := slack.ItemRef{
		Channel:   event.Channel,
		Timestamp: event.Timestamp,
	}
	err := b.slackBotAPI.AddReaction(reaction, item)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to add reaction to slack.")
		return
	}
}

func (b *Bot) sourceCode(event *slack.MessageEvent) {
	params := slack.PostMessageParameters{AsUser: true}
	_, _, err := b.slackBotAPI.PostMessage(event.Channel, "My source code is here <https://github.com/pastjean/scrumpolice>", params)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to post message to slack.")
		return
	}
}

func (b *Bot) help(event *slack.MessageEvent) {
	message := slack.Attachment{
		MarkdownIn: []string{"text"},
		Text: "- `source code`: location of my source code\n" +
			"- `help`: well, this command\n" +
			"- `tutorial`: explains how the scrum police works. Try it!\n" +
			"- `start`: starts a scrum for a team and a specific set of questions, defaults to your only team if you got only one, and only questions set if there's only one on the team you chose\n" +
			"- `restart`: restart your last done scrum, if it wasn't posted\n" +
			"- `out of office`: mark current user as out of office (until `i'm back` is used)\n" +
			"- `[user] is out of office`: mark the specified user as out of office (until he or she uses `i'm back`)\n" +
			"- `i am back` or `i'm back`: mark current user as in office. MacOS smart quote can screw up with the `i'm back` command.",
	}

	params := slack.PostMessageParameters{AsUser: true}
	params.Attachments = []slack.Attachment{message}

	_, _, err := b.slackBotAPI.PostMessage(event.Channel, "Here's a list of supported commands", params)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to post message to slack.")
		return
	}
}

// This method sleeps to give a better feeling to the user. It should be use in a sub-routine.
func (b *Bot) tutorial(event *slack.MessageEvent) {
	params := slack.PostMessageParameters{AsUser: true}

	b.slackBotAPI.PostMessage(event.Channel,
		"*Hi there* :wave: You're new, aren't you? You want to know how I do thing? Here :golang:es!\n"+
			"When you want to start a scrum report, just tell me `start` in a direct message :flag-dm:. _If you are part of more than one team, specify the team (I will ask you if you don't)_\n"+
			"Then, I will ask you a couple of questions, and wait for your answers. Once you anwsered all the questions, you're done :white_check_mark:.\n"+
			"I take care of the rest! :cop:\n"+
			"When it's time :clock10:, I will post the scrum report for you and your friends in your team's channel :raised_hands:\n"+
			"All you have to do now is read the report :book: (when you have the time, I don't want to rush you :scream:)\n"+
			"That's all. Enjoy :beers:.",
		params)
}

func (b *Bot) outOfOffice(event *slack.MessageEvent, userId string, resolveUser bool) {
	params := slack.PostMessageParameters{AsUser: true}
	username := strings.TrimLeft(userId, "@")

	if resolveUser {
		user, err := b.slackBotAPI.GetUserInfo(username)
		if err != nil {
			b.logSlackRelatedError(event, err, "Fail to get user information.")
			b.slackBotAPI.PostMessage(event.Channel, "Hmmmm, I couldn't find you. Try again!", params)
			return
		}
		username = user.Name
	}

	teams := b.scrum.GetTeamsForUser(username)

	for _, team := range teams {
		b.scrum.AddToOutOfOffice(team, username)
	}
	if event.User == userId {
		b.slackBotAPI.PostMessage(event.Channel, "I've marked you out of office in all your teams", params)
		log.WithFields(log.Fields{
			"user":   username,
			"doneBy": username,
		}).Info("User was marked out of office.")
	} else {
		b.slackBotAPI.PostMessage(event.Channel, "I've marked @"+username+" out of office in all of his teams", params)

		user, err := b.slackBotAPI.GetUserInfo(event.User)
		if err != nil {
			b.logSlackRelatedError(event, err, "Fail to get user information.")
			return
		}
		b.slackBotAPI.PostMessage("@"+username, "You've been marked out of office by @"+user.Name+".", params)
		log.WithFields(log.Fields{
			"user":   userId,
			"doneBy": user.Name,
		}).Info("User was marked out of office.")
	}
}

func (b *Bot) backInOffice(event *slack.MessageEvent) {
	params := slack.PostMessageParameters{AsUser: true}
	user, err := b.slackBotAPI.GetUserInfo(event.User)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to get user information.")
		b.slackBotAPI.PostMessage(event.Channel, "Hmmmm, I couldn't find you. Try again!", params)
		return
	}
	username := user.Name

	teams := b.scrum.GetTeamsForUser(username)

	for _, team := range teams {
		b.scrum.RemoveFromOutOfOffice(team, username)
	}
	b.slackBotAPI.PostMessage(event.Channel, "I've marked you in office in all your teams. Welcome back!", params)
	log.WithFields(log.Fields{
		"user": username,
	}).Info("User was marked in office.")
}

func (b *Bot) unrecognizedMessage(event *slack.MessageEvent) {
	log.WithFields(log.Fields{
		"text": event.Text,
		"user": event.Username,
	}).Info("Received unrecognized message.")
	params := slack.PostMessageParameters{AsUser: true}

	_, _, err := b.slackBotAPI.PostMessage(event.Channel, "I don't understand what you're trying to tell me, try `help`", params)
	if err != nil {
		b.logSlackRelatedError(event, err, "Fail to post message to slack.")
		return
	}
}

func (b *Bot) canQuitBotContextHandlerFunc(handler func(event *slack.MessageEvent) bool) BotContextHandler {
	return b.canQuitBotContext(BotContextHandlerFunc(handler))
}

func (b *Bot) canQuitBotContext(handler BotContextHandler) BotContextHandler {
	return BotContextHandlerFunc(func(event *slack.MessageEvent) bool {
		if event.Text == "quit" {
			b.slackBotAPI.PostMessage(event.Channel, "Action is canceled, if you wanna do anything else, just poke me, `help` is always available! :wave:", slack.PostMessageParameters{AsUser: true})
			delete(b.userContexts, event.User)
			return false
		}

		return handler.HandleMessage(event)
	})
}

func (b *Bot) setUserContext(user string, context BotContextHandler) {
	b.userContextsMutex.Lock()
	b.userContexts[user] = context
	b.userContextsMutex.Unlock()
}

func (b *Bot) unsetUserContext(user string) {
	b.userContextsMutex.Lock()
	delete(b.userContexts, user)
	b.userContextsMutex.Unlock()
}

func (b *Bot) logSlackRelatedError(event *slack.MessageEvent, err error, logMessage string) {
	b.logger.WithFields(log.Fields{
		"text":  event.Text,
		"user":  event.Username,
		"error": err,
	}).Error(logMessage)
}
<|endoftext|>


<commit_before>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package web

import (
	"github.com/gorilla/mux"
	"github.com/zenoss/glog"
	"github.com/zenoss/serviced/domain/servicestate"
	"github.com/zenoss/serviced/zzk/registry"

	"crypto/tls"
	"errors"
	"fmt"
	"github.com/zenoss/serviced/coordinator/client"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"sync"
	"time"
)

var (
	vregistry = vhostRegistry{lookup: make(map[string]*vhostInfo), vhostWatch: make(map[string]chan <- bool)}
)

type vhostInfo struct {
	sync.RWMutex
	endpoints []vhostEndpointInfo
	counter   int
}

func newVhostInfo() *vhostInfo {
	return &vhostInfo{endpoints: make([]vhostEndpointInfo, 0)}
}

func (vi *vhostInfo) GetNext() (vhostEndpointInfo, error) {
	vi.Lock()
	defer vi.Unlock()
	if len(vi.endpoints) == 0 {
		return vhostEndpointInfo{}, errors.New("no vhost endpoints available")
	}
	vep := vi.endpoints[vi.counter%len(vi.endpoints)]
	vi.counter++
	return vep, nil
}

type vhostEndpointInfo struct {
	hostIP    string
	epPort    uint16
	privateIP string
}

func createvhostEndpointInfo(vep *registry.VhostEndpoint) vhostEndpointInfo {
	return vhostEndpointInfo{
		hostIP:    vep.HostIP,
		epPort:    vep.ContainerPort,
		privateIP: vep.ContainerIP,
	}

}

func createVhostInfos(state *servicestate.ServiceState) map[string]*vhostInfo {
	infos := make(map[string]*vhostInfo)

	for _, svcep := range state.Endpoints {
		for _, vhost := range svcep.VHosts {
			if _, found := infos[vhost]; !found {
				infos[vhost] = newVhostInfo()
			}
			vi := vhostEndpointInfo{
				hostIP:    state.HostIP,
				epPort:    svcep.PortNumber,
				privateIP: state.PrivateIP,
			}
			info := infos[vhost]
			info.endpoints = append(infos[vhost].endpoints, vi)
		}
	}
	glog.Infof("created vhost infos %#v", infos)
	return infos
}

//vhostRegistry keeps track of all current known vhosts and vhost endpoints.
type vhostRegistry struct {
	sync.RWMutex
	lookup     map[string]*vhostInfo   //vhost name to all avaialbe endpoints
	vhostWatch map[string]chan <- bool //watches to ZK vhost dir  e.g. zenoss5x. Channel is to cancel watch
}

//get returns a vhostInfo, bool is true or false if vhost is found
func (vr *vhostRegistry) get(vhost string) (*vhostInfo, bool) {
	vr.RLock()
	defer vr.RUnlock()
	vhInfo, found := vr.lookup[vhost]
	if !found {
		glog.V(4).Infof("vhost %v not found in map %v", vhost, vr.lookup)
	}
	return vhInfo, found
}

//setEndpoints sets/replaces all the endpoints available for a vhost
func (vr *vhostRegistry) setVhostInfo(vhost string, vhInfo *vhostInfo) {
	vr.Lock()
	defer vr.Unlock()
	vr.lookup[vhost] = vhInfo
	glog.Infof("setVhostInfo adding VHost %v with backend: %#v", vhost, vhInfo)
}

//replaces all the vhost lookup information
func (vr *vhostRegistry) setAll(vhosts map[string]*vhostInfo) {
	vr.Lock()
	defer vr.Unlock()
	vr.lookup = make(map[string]*vhostInfo)
	for key, infos := range vhosts {
		vr.lookup[key] = infos
		for _, ep := range infos.endpoints {
			glog.Infof("vhosthandler adding VHost %v with backend: %#v", key, ep)
		}
	}
}

func (sc *ServiceConfig) syncVhosts() {
	go sc.watchVhosts()
}

func (sc *ServiceConfig) watchVhosts() error {
	glog.Info("watchVhosts starting...")
	conn, err := sc.zkClient.GetConnection()
	if err != nil {
		glog.Errorf("watchVhosts - Error getting zk connection: %v", err)
		return err
	}

	vhostRegistry, err := registry.VHostRegistry(conn)
	if err != nil {
		glog.Errorf("watchVhosts - Error getting vhost registry: %v", err)
		return err
	}

	processVhosts := func(conn client.Connection, parentPath string, childIDs ...string) {
		glog.Infof("processVhosts STARTING for parentPath:%s childIDs:%v", parentPath, childIDs)

		currentVhosts := make(map[string]struct{})
		//watch any new vhost nodes
		for _, vhostID := range childIDs {
			vhostPath := fmt.Sprintf("%s/%s", parentPath, vhostID)
			currentVhosts[vhostPath] = struct{}{}
			if _, found := vregistry.vhostWatch[vhostPath]; !found {
				glog.Infof("processing vhost watch: %s", vhostPath)
				cancelChan := make(chan bool)
				vregistry.vhostWatch[vhostPath] = cancelChan
				go vhostRegistry.WatchKey(conn, vhostID, cancelChan, sc.processVhost(vhostID), vhostWatchError)
			} else {
				glog.Infof("vhost %s already being watched", vhostPath)
			}
		}

		//cancel watching any vhosts nodes that are no longer
		for previousVhost, cancel := range vregistry.vhostWatch {
			if _, found := currentVhosts[previousVhost]; !found {
				glog.Infof("Cancelling vhost watch for %s}", previousVhost)
				delete(vregistry.vhostWatch, previousVhost)
				cancel <- true
				close(cancel)
			}
		}
	}

	cancelChan := make(chan bool)
	vhostRegistry.WatchRegistry(conn, cancelChan, processVhosts, vhostWatchError)
	glog.Warning("watchVhosts ended")

	return nil
}

//processVhost is used to watch the children of particular vhost in the registry
func (sc *ServiceConfig) processVhost(vhostID string) registry.ProcessChildrenFunc {

	return func(conn client.Connection, parentPath string, childIDs ...string) {
		glog.Infof("processing vhost parent %v; children %v", parentPath, childIDs)
		vr, err := registry.VHostRegistry(conn)
		if err != nil {
			glog.Errorf("processVhost - Error getting vhost registry: %v", err)
			return
		}

		vhostEndpoints := newVhostInfo()
		for _, child := range childIDs {
			vhEndpoint, err := vr.GetItem(conn, parentPath + "/" + child)
			if err != nil {
				glog.Errorf("processVhost - Error getting vhost for %v/%v: %v", parentPath, child, err)
				continue
			}
			glog.Infof("Processing vhost %s/%s: %#v", parentPath, child, vhEndpoint)
			vepInfo := createvhostEndpointInfo(vhEndpoint)
			vhostEndpoints.endpoints = append(vhostEndpoints.endpoints, vepInfo)
		}
		vregistry.setVhostInfo(vhostID, vhostEndpoints)
	}
}

func vhostWatchError(path string, err error) {
	glog.Infof("processing vhostWatchError on %s: %v", path, err)

}

// Lookup the appropriate virtual host and forward the request to it.
// TODO: when zookeeper registration is integrated we can be more event
// driven and only refresh the vhost map when service states change.
func (sc *ServiceConfig) vhosthandler(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	glog.V(0).Infof("vhosthandler handling: %+v", r)
	muxvars := mux.Vars(r)
	subdomain := muxvars["subdomain"]
	glog.V(0).Infof("muxvars: %+v", muxvars)
	glog.V(0).Infof("subdomain: %+v", subdomain)

	defer func() {
		glog.V(1).Infof("Time to process %s vhost request %v: %v", subdomain, r.URL, time.Since(start))
	}()

	vhInfo, found := vregistry.get(subdomain)
	if !found {
		http.Error(w, fmt.Sprintf("service associated with vhost %v is not running", subdomain), http.StatusNotFound)
		return
	}
	// TODO: implement a more intelligent strategy than "always pick the first one" when more
	// than one service state is mapped to a given virtual host
	vhEP, err := vhInfo.GetNext()
	if err != nil {
		glog.V(4).Infof("no endpoing found for vhost %s: %v", subdomain, err)
		http.Error(w, fmt.Sprintf("no available service for vhost %v ", subdomain), http.StatusNotFound)
		return
	}
	remoteAddr := fmt.Sprintf("%s:%d", vhEP.hostIP, vhEP.epPort)
	if sc.muxTLS && (sc.muxPort > 0) { // Only do TLS if connecting to a TCPMux
		remoteAddr = fmt.Sprintf("%s:%d", vhEP.hostIP, sc.muxPort)
	}
	rp := getReverseProxy(remoteAddr, sc.muxPort, vhEP.privateIP, vhEP.epPort, sc.muxTLS && (sc.muxPort > 0))
	glog.V(0).Infof("vhost proxy remoteAddr:%s sc.muxPort:%s vhEP.privateIP:%s vhEP.epPort:%s", remoteAddr, sc.muxPort, vhEP.privateIP, vhEP.epPort)
	glog.V(0).Infof("Time to set up %s vhost proxy for %v: %v", subdomain, r.URL, time.Since(start))
	rp.ServeHTTP(w, r)
	return

}

var reverseProxies map[string]*httputil.ReverseProxy
var reverseProxiesLock sync.Mutex

func init() {
	reverseProxies = make(map[string]*httputil.ReverseProxy)
}

func getReverseProxy(remoteAddr string, muxPort int, privateIP string, privatePort uint16, useTLS bool) *httputil.ReverseProxy {

	reverseProxiesLock.Lock()
	defer reverseProxiesLock.Unlock()

	key := fmt.Sprintf("%s,%d,%s,%s,%v", remoteAddr, muxPort, privateIP, privatePort, useTLS)
	proxy, ok := reverseProxies[key]
	if ok {
		return proxy
	}

	rpurl := url.URL{Scheme: "http", Host: remoteAddr}

	glog.V(1).Infof("vhosthandler reverse proxy to: %v", rpurl)

	transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
	transport.Dial = func(network, addr string) (remote net.Conn, err error) {
		if useTLS { // Only do TLS if connecting to a TCPMux
			config := tls.Config{InsecureSkipVerify: true}
			glog.V(1).Infof("vhost about to dial %s", remoteAddr)
			remote, err = tls.Dial("tcp4", remoteAddr, &config)
		} else {
			glog.V(1).Info("vhost about to dial %s", remoteAddr)
			remote, err = net.Dial("tcp4", remoteAddr)
		}
		if err != nil {
			return nil, err
		}

		if muxPort > 0 {
			//TODO: move this check to happen sooner
			if len(privateIP) == 0 {
				return nil, fmt.Errorf("missing endpoint")
			}
			muxAddr := fmt.Sprintf("%s:%d\n", privateIP, privatePort)
			glog.V(1).Infof("vhost muxing to %s", muxAddr)
			io.WriteString(remote, muxAddr)

		}
		return remote, nil
	}
	transport.DisableCompression = true
	transport.DisableKeepAlives = true
	rp := httputil.NewSingleHostReverseProxy(&rpurl)
	rp.Transport = transport
	rp.FlushInterval = time.Second

	reverseProxies[key] = rp
	return rp

}
<commit_msg>reduced spam<commit_after>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package web

import (
	"github.com/gorilla/mux"
	"github.com/zenoss/glog"
	"github.com/zenoss/serviced/domain/servicestate"
	"github.com/zenoss/serviced/zzk/registry"

	"crypto/tls"
	"errors"
	"fmt"
	"github.com/zenoss/serviced/coordinator/client"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"sync"
	"time"
)

var (
	vregistry = vhostRegistry{lookup: make(map[string]*vhostInfo), vhostWatch: make(map[string]chan<- bool)}
)

type vhostInfo struct {
	sync.RWMutex
	endpoints []vhostEndpointInfo
	counter   int
}

func newVhostInfo() *vhostInfo {
	return &vhostInfo{endpoints: make([]vhostEndpointInfo, 0)}
}

func (vi *vhostInfo) GetNext() (vhostEndpointInfo, error) {
	vi.Lock()
	defer vi.Unlock()
	if len(vi.endpoints) == 0 {
		return vhostEndpointInfo{}, errors.New("no vhost endpoints available")
	}
	vep := vi.endpoints[vi.counter%len(vi.endpoints)]
	vi.counter++
	return vep, nil
}

type vhostEndpointInfo struct {
	hostIP    string
	epPort    uint16
	privateIP string
}

func createvhostEndpointInfo(vep *registry.VhostEndpoint) vhostEndpointInfo {
	return vhostEndpointInfo{
		hostIP:    vep.HostIP,
		epPort:    vep.ContainerPort,
		privateIP: vep.ContainerIP,
	}

}

func createVhostInfos(state *servicestate.ServiceState) map[string]*vhostInfo {
	infos := make(map[string]*vhostInfo)

	for _, svcep := range state.Endpoints {
		for _, vhost := range svcep.VHosts {
			if _, found := infos[vhost]; !found {
				infos[vhost] = newVhostInfo()
			}
			vi := vhostEndpointInfo{
				hostIP:    state.HostIP,
				epPort:    svcep.PortNumber,
				privateIP: state.PrivateIP,
			}
			info := infos[vhost]
			info.endpoints = append(infos[vhost].endpoints, vi)
		}
	}
	glog.Infof("created vhost infos %#v", infos)
	return infos
}

//vhostRegistry keeps track of all current known vhosts and vhost endpoints.
type vhostRegistry struct {
	sync.RWMutex
	lookup     map[string]*vhostInfo  //vhost name to all availabe endpoints
	vhostWatch map[string]chan<- bool //watches to ZK vhost dir  e.g. zenoss5x. Channel is to cancel watch
}

//get returns a vhostInfo, bool is true or false if vhost is found
func (vr *vhostRegistry) get(vhost string) (*vhostInfo, bool) {
	vr.RLock()
	defer vr.RUnlock()
	vhInfo, found := vr.lookup[vhost]
	if !found {
		glog.V(4).Infof("vhost %v not found in map %v", vhost, vr.lookup)
	}
	return vhInfo, found
}

//setEndpoints sets/replaces all the endpoints available for a vhost
func (vr *vhostRegistry) setVhostInfo(vhost string, vhInfo *vhostInfo) {
	vr.Lock()
	defer vr.Unlock()
	vr.lookup[vhost] = vhInfo
	glog.Infof("setVhostInfo adding VHost %v with backend: %#v", vhost, vhInfo)
}

//replaces all the vhost lookup information
func (vr *vhostRegistry) setAll(vhosts map[string]*vhostInfo) {
	vr.Lock()
	defer vr.Unlock()
	vr.lookup = make(map[string]*vhostInfo)
	for key, infos := range vhosts {
		vr.lookup[key] = infos
		for _, ep := range infos.endpoints {
			glog.Infof("vhosthandler adding VHost %v with backend: %#v", key, ep)
		}
	}
}

func (sc *ServiceConfig) syncVhosts() {
	go sc.watchVhosts()
}

func (sc *ServiceConfig) watchVhosts() error {
	glog.Info("watchVhosts starting...")
	conn, err := sc.zkClient.GetConnection()
	if err != nil {
		glog.Errorf("watchVhosts - Error getting zk connection: %v", err)
		return err
	}

	vhostRegistry, err := registry.VHostRegistry(conn)
	if err != nil {
		glog.Errorf("watchVhosts - Error getting vhost registry: %v", err)
		return err
	}

	processVhosts := func(conn client.Connection, parentPath string, childIDs ...string) {
		glog.Infof("processVhosts STARTING for parentPath:%s childIDs:%v", parentPath, childIDs)

		currentVhosts := make(map[string]struct{})
		//watch any new vhost nodes
		for _, vhostID := range childIDs {
			vhostPath := fmt.Sprintf("%s/%s", parentPath, vhostID)
			currentVhosts[vhostPath] = struct{}{}
			if _, found := vregistry.vhostWatch[vhostPath]; !found {
				glog.Infof("processing vhost watch: %s", vhostPath)
				cancelChan := make(chan bool)
				vregistry.vhostWatch[vhostPath] = cancelChan
				go vhostRegistry.WatchKey(conn, vhostID, cancelChan, sc.processVhost(vhostID), vhostWatchError)
			} else {
				glog.Infof("vhost %s already being watched", vhostPath)
			}
		}

		//cancel watching any vhosts nodes that are no longer
		for previousVhost, cancel := range vregistry.vhostWatch {
			if _, found := currentVhosts[previousVhost]; !found {
				glog.Infof("Cancelling vhost watch for %s}", previousVhost)
				delete(vregistry.vhostWatch, previousVhost)
				cancel <- true
				close(cancel)
			}
		}
	}

	cancelChan := make(chan bool)
	vhostRegistry.WatchRegistry(conn, cancelChan, processVhosts, vhostWatchError)
	glog.Warning("watchVhosts ended")

	return nil
}

//processVhost is used to watch the children of particular vhost in the registry
func (sc *ServiceConfig) processVhost(vhostID string) registry.ProcessChildrenFunc {

	return func(conn client.Connection, parentPath string, childIDs ...string) {
		glog.Infof("processing vhost parent %v; children %v", parentPath, childIDs)
		vr, err := registry.VHostRegistry(conn)
		if err != nil {
			glog.Errorf("processVhost - Error getting vhost registry: %v", err)
			return
		}

		vhostEndpoints := newVhostInfo()
		for _, child := range childIDs {
			vhEndpoint, err := vr.GetItem(conn, parentPath+"/"+child)
			if err != nil {
				glog.Errorf("processVhost - Error getting vhost for %v/%v: %v", parentPath, child, err)
				continue
			}
			glog.Infof("Processing vhost %s/%s: %#v", parentPath, child, vhEndpoint)
			vepInfo := createvhostEndpointInfo(vhEndpoint)
			vhostEndpoints.endpoints = append(vhostEndpoints.endpoints, vepInfo)
		}
		vregistry.setVhostInfo(vhostID, vhostEndpoints)
	}
}

func vhostWatchError(path string, err error) {
	glog.Warningf("processing vhostWatchError on %s: %v", path, err)

}

// Lookup the appropriate virtual host and forward the request to it.
// TODO: when zookeeper registration is integrated we can be more event
// driven and only refresh the vhost map when service states change.
func (sc *ServiceConfig) vhosthandler(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	glog.V(1).Infof("vhosthandler handling: %+v", r)
	muxvars := mux.Vars(r)
	subdomain := muxvars["subdomain"]
	glog.V(1).Infof("muxvars: %+v", muxvars)

	defer func() {
		glog.V(1).Infof("Time to process %s vhost request %v: %v", subdomain, r.URL, time.Since(start))
	}()

	vhInfo, found := vregistry.get(subdomain)
	if !found {
		http.Error(w, fmt.Sprintf("service associated with vhost %v is not running", subdomain), http.StatusNotFound)
		return
	}
	// TODO: implement a more intelligent strategy than "always pick the first one" when more
	// than one service state is mapped to a given virtual host
	vhEP, err := vhInfo.GetNext()
	if err != nil {
		glog.V(4).Infof("no endpoing found for vhost %s: %v", subdomain, err)
		http.Error(w, fmt.Sprintf("no available service for vhost %v ", subdomain), http.StatusNotFound)
		return
	}
	remoteAddr := fmt.Sprintf("%s:%d", vhEP.hostIP, vhEP.epPort)
	if sc.muxTLS && (sc.muxPort > 0) { // Only do TLS if connecting to a TCPMux
		remoteAddr = fmt.Sprintf("%s:%d", vhEP.hostIP, sc.muxPort)
	}
	rp := getReverseProxy(remoteAddr, sc.muxPort, vhEP.privateIP, vhEP.epPort, sc.muxTLS && (sc.muxPort > 0))
	glog.V(1).Infof("vhost proxy remoteAddr:%s sc.muxPort:%s vhEP.privateIP:%s vhEP.epPort:%s", remoteAddr, sc.muxPort, vhEP.privateIP, vhEP.epPort)
	glog.V(1).Infof("Time to set up %s vhost proxy for %v: %v", subdomain, r.URL, time.Since(start))
	rp.ServeHTTP(w, r)
	return

}

var reverseProxies map[string]*httputil.ReverseProxy
var reverseProxiesLock sync.Mutex

func init() {
	reverseProxies = make(map[string]*httputil.ReverseProxy)
}

func getReverseProxy(remoteAddr string, muxPort int, privateIP string, privatePort uint16, useTLS bool) *httputil.ReverseProxy {

	reverseProxiesLock.Lock()
	defer reverseProxiesLock.Unlock()

	key := fmt.Sprintf("%s,%d,%s,%s,%v", remoteAddr, muxPort, privateIP, privatePort, useTLS)
	proxy, ok := reverseProxies[key]
	if ok {
		return proxy
	}

	rpurl := url.URL{Scheme: "http", Host: remoteAddr}

	glog.V(1).Infof("vhosthandler reverse proxy to: %v", rpurl)

	transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
	transport.Dial = func(network, addr string) (remote net.Conn, err error) {
		if useTLS { // Only do TLS if connecting to a TCPMux
			config := tls.Config{InsecureSkipVerify: true}
			glog.V(1).Infof("vhost about to dial %s", remoteAddr)
			remote, err = tls.Dial("tcp4", remoteAddr, &config)
		} else {
			glog.V(1).Info("vhost about to dial %s", remoteAddr)
			remote, err = net.Dial("tcp4", remoteAddr)
		}
		if err != nil {
			return nil, err
		}

		if muxPort > 0 {
			//TODO: move this check to happen sooner
			if len(privateIP) == 0 {
				return nil, fmt.Errorf("missing endpoint")
			}
			muxAddr := fmt.Sprintf("%s:%d\n", privateIP, privatePort)
			glog.V(1).Infof("vhost muxing to %s", muxAddr)
			io.WriteString(remote, muxAddr)

		}
		return remote, nil
	}
	transport.DisableCompression = true
	transport.DisableKeepAlives = true
	rp := httputil.NewSingleHostReverseProxy(&rpurl)
	rp.Transport = transport
	rp.FlushInterval = time.Second

	reverseProxies[key] = rp
	return rp

}
<|endoftext|>


<commit_before>// errorcheck

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Test that incorrect expressions involving wrong anonymous interface
// do not generate panics in Type Stringer.
// Does not compile.

package main

type I interface {
	int // ERROR "interface contains embedded non-interface"
}

func n() {
	(I)
}

func m() {
	(interface{int}) // ERROR "interface contains embedded non-interface int" "type interface { int } is not an expression"
}

func main() {
}
<commit_msg>test: match gofrontend error messages<commit_after>// errorcheck

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Test that incorrect expressions involving wrong anonymous interface
// do not generate panics in Type Stringer.
// Does not compile.

package main

type I interface {
	int // ERROR "interface contains embedded non-interface"
}

func n() {
	(I)
}

func m() {
	(interface{int}) // ERROR "interface contains embedded non-interface" "type interface { int } is not an expression"
}

func main() {
}
<|endoftext|>


<commit_before>package main

import "testing"

func Test_Execute_Ok(t *testing.T) {
	stdout, stderr, err := Execute("date")
	if err != nil {
		t.Error(err)
	}
	if len(stdout) != 1 {
		t.Error("stdout lenght not equal")
	}
	if len(stderr) != 0 {
		t.Error("stderr lenght not equal")
	}
}

func Test_Execute_Err(t *testing.T) {
	stdout, stderr, err := Execute("go", "build", "fixture/foo.src")
	if err != nil {
		t.Error(err)
	}
	if len(stdout) != 0 {
		t.Error("stdout lenght not equal")
	}
	if len(stderr) != 3 {
		t.Error("stderr lenght not equal")
	}
}
<commit_msg>rename execute package at test<commit_after>package execute

import "testing"

func Test_Execute_Ok(t *testing.T) {
	stdout, stderr, err := Execute("date")
	if err != nil {
		t.Error(err)
	}
	if len(stdout) != 1 {
		t.Error("stdout lenght not equal")
	}
	if len(stderr) != 0 {
		t.Error("stderr lenght not equal")
	}
}

func Test_Execute_Err(t *testing.T) {
	stdout, stderr, err := Execute("go", "build", "fixture/foo.src")
	if err != nil {
		t.Error(err)
	}
	if len(stdout) != 0 {
		t.Error("stdout lenght not equal")
	}
	if len(stderr) != 3 {
		t.Error("stderr lenght not equal")
	}
}
<|endoftext|>


<commit_before>package dsl_test

import (
	"testing"

	"reflect"

	"github.com/goadesign/goa/design"
	. "github.com/goadesign/goa/design/dsl"
	"github.com/goadesign/goa/eval"
)

func TestEndpoint(t *testing.T) {
	cases := map[string]struct {
		Expr   eval.Expression
		DSL    func()
		Assert map[string]func(t *testing.T, s *design.EndpointExpr)
	}{
		"basic": {
			&design.ServiceExpr{},
			func() {
				Endpoint("basic", func() {
					Description("Optional description")
					// Docs allows linking to external documentation.
					Docs(func() {
						Description("Optional description")
						URL("https://goa.design")
					})
					Request(func() {
						Description("Optional description")
						Attribute("required", design.String)
						Required("required")
					})
					Response(func() {
						Description("Optional description")
						Attribute("required", design.String)
						Required("required")
					})
					Error("basic_error")
					Error("basic_media_error", design.ErrorMedia)
					Metadata("name", "some value", "some other value")
				})
				Endpoint("another", func() {
					// Docs allows linking to external documentation.
					Docs(func() {
						Description("Optional description")
						URL("https://goa.design")
					})
					Request(design.String)
					Response(design.String)
					Error("basic_media_error", design.ErrorMedia)
				})
			},
			map[string]func(t *testing.T, s *design.EndpointExpr){
				"basic": func(t *testing.T, e *design.EndpointExpr) {
					assertEndpointDescription(t, "Optional description", e.Description)
					assertEndpointDocs(t, e.Docs, "https://goa.design", "Optional description")
					if len(e.Errors) != 2 {
						t.Errorf("expected %d error definitions but got %d ", 2, len(e.Errors))
					}
					assertEndpointError(t, e.Errors[0], "basic_error", design.ErrorMedia)
					assertEndpointError(t, e.Errors[1], "basic_media_error", design.ErrorMedia)
					expectedMeta := design.MetadataExpr{
						"name": []string{"some value", "some other value"},
					}
					assertEndpointMetaData(t, e.Metadata, expectedMeta)
					expectedReq := &design.UserTypeExpr{
						TypeName:      "BasicRequest",
						AttributeExpr: &design.AttributeExpr{Description: "Optional description", Type: &design.Object{}}}
					assertEndpointRequestResponse(t, "Request", e.Request, expectedReq)
					expectedRes := &design.UserTypeExpr{
						TypeName:      "BasicResponse",
						AttributeExpr: &design.AttributeExpr{Description: "Optional description", Type: &design.Object{}}}
					assertEndpointRequestResponse(t, "Response", e.Response, expectedRes)
				},
				"another": func(t *testing.T, e *design.EndpointExpr) {
					assertEndpointDocs(t, e.Docs, "https://goa.design", "Optional description")
					if len(e.Errors) != 1 {
						t.Errorf("expected %d error definitions but got %d ", 1, len(e.Errors))
					}
					assertEndpointError(t, e.Errors[0], "basic_media_error", design.ErrorMedia)
					expectedReq := &design.UserTypeExpr{TypeName: "AnotherRequest", AttributeExpr: &design.AttributeExpr{Type: design.String}}
					assertEndpointRequestResponse(t, "Request", e.Request, expectedReq)
					expectedRes := &design.UserTypeExpr{TypeName: "AnotherResponse", AttributeExpr: &design.AttributeExpr{Type: design.String}}
					assertEndpointRequestResponse(t, "Response", e.Response, expectedRes)
				},
			},
		},
	}
	//Run our tests
	for k, tc := range cases {
		t.Run(k, func(t *testing.T) {
			eval.Context = &eval.DSLContext{}
			eval.Execute(tc.DSL, tc.Expr)
			evalService := tc.Expr.(*design.ServiceExpr)
			if eval.Context.Errors != nil {
				t.Errorf("%s: Endpoint failed unexpectedly with %s", k, eval.Context.Errors)
			}
			for _, e := range evalService.Endpoints {
				if _, ok := tc.Assert[e.Name]; !ok {
					t.Errorf("no assert found for endpoint %s ", e.Name)
					break
				}
				tc.Assert[e.Name](t, e)
			}
		})
	}
}

//helper funcs
func assertEndpointDocs(t *testing.T, doc *design.DocsExpr, url, desc string) {
	if doc.Description != desc {
		t.Errorf("expected docs description '%s' to match '%s' ", desc, doc.Description)
	}
	if doc.URL != url {
		t.Errorf("expected docs url '%s' to match '%s' ", url, doc.URL)
	}
}

func assertEndpointDescription(t *testing.T, expectedDesc, actualDesc string) {
	if expectedDesc != actualDesc {
		t.Errorf("expected description '%s' to match '%s' ", actualDesc, expectedDesc)
	}
}

func assertEndpointError(t *testing.T, actual *design.ErrorExpr, name string, dt design.DataType) {
	if actual.Name != name {
		t.Errorf("expected error to have name %s but got %s ", name, actual.Name)
	}

	if actual.AttributeExpr.Type != dt {
		t.Errorf("expected the error DataType to be %v but got %v ", dt, actual.AttributeExpr.Type)
	}
}

func assertEndpointMetaData(t *testing.T, actual design.MetadataExpr, expected design.MetadataExpr) {
	for key, val := range actual {
		vals, ok := expected[key]
		if !ok {
			t.Errorf("metaData was missing expected key %s ", key)
			continue
		}
		for _, metaVal := range val {
			if !hasValue(vals, metaVal) {
				t.Errorf("metaData was missing expected value %s ", metaVal)
			}
		}

	}
}

func assertEndpointRequestResponse(t *testing.T, assertType string, actual design.DataType, expected *design.UserTypeExpr) {
	ut, ok := actual.(*design.UserTypeExpr)
	if !ok || ut == nil {
		t.Errorf("expected endpoint %s to be a *UserTypeExpr but got %v", assertType, reflect.TypeOf(ut))
	}
	if ut.Name() != expected.Name() {
		t.Errorf("expected endpoint %s name %s to match %s", assertType, ut.Name(), expected.Name())
	}
	if ut.AttributeExpr.Type.Name() != expected.Type.Name() {
		t.Errorf("expected endpoint %s TypeName %s to match %s ", assertType, ut.Type.Name(), expected.Type.Name())
	}
	if ut.Description != expected.Description {
		t.Errorf("expected endpoint %s description %s to match %s", assertType, ut.Description, expected.Description)
	}

}
<commit_msg>fix nill check and add a couple extra asserts<commit_after>package dsl_test

import (
	"testing"

	"reflect"

	"github.com/goadesign/goa/design"
	. "github.com/goadesign/goa/design/dsl"
	"github.com/goadesign/goa/eval"
)

func TestEndpoint(t *testing.T) {
	cases := map[string]struct {
		Expr   eval.Expression
		DSL    func()
		Assert map[string]func(t *testing.T, s *design.EndpointExpr)
	}{
		"basic": {
			&design.ServiceExpr{},
			func() {
				Endpoint("basic", func() {
					Description("Optional description")
					// Docs allows linking to external documentation.
					Docs(func() {
						Description("Optional description")
						URL("https://goa.design")
					})
					Request(func() {
						Description("Optional description")
						Attribute("required", design.String)
						Required("required")
					})
					Response(func() {
						Description("Optional description")
						Attribute("required", design.String)
						Required("required")
					})
					Error("basic_error")
					Error("basic_media_error", design.ErrorMedia)
					Metadata("name", "some value", "some other value")
				})
				Endpoint("another", func() {
					// Docs allows linking to external documentation.
					Docs(func() {
						Description("Optional description")
						URL("https://goa.design")
					})
					Request(design.String)
					Response(design.String)
					Error("basic_media_error", design.ErrorMedia)
				})
			},
			map[string]func(t *testing.T, s *design.EndpointExpr){
				"basic": func(t *testing.T, e *design.EndpointExpr) {
					assertEndpointDescription(t, "Optional description", e.Description)
					assertEndpointDocs(t, e.Docs, "https://goa.design", "Optional description")
					if len(e.Errors) != 2 {
						t.Errorf("expected %d error definitions but got %d ", 2, len(e.Errors))
					}
					assertEndpointError(t, e.Errors[0], "basic_error", design.ErrorMedia)
					assertEndpointError(t, e.Errors[1], "basic_media_error", design.ErrorMedia)
					expectedMeta := design.MetadataExpr{
						"name": []string{"some value", "some other value"},
					}
					assertEndpointMetaData(t, e.Metadata, expectedMeta)
					expectedReq := &design.UserTypeExpr{
						TypeName:      "BasicRequest",
						AttributeExpr: &design.AttributeExpr{Description: "Optional description", Type: &design.Object{}}}
					assertEndpointRequestResponse(t, "Request", e.Request, expectedReq)
					expectedRes := &design.UserTypeExpr{
						TypeName:      "BasicResponse",
						AttributeExpr: &design.AttributeExpr{Description: "Optional description", Type: &design.Object{}}}
					assertEndpointRequestResponse(t, "Response", e.Response, expectedRes)
				},
				"another": func(t *testing.T, e *design.EndpointExpr) {
					assertEndpointDocs(t, e.Docs, "https://goa.design", "Optional description")
					if len(e.Errors) != 1 {
						t.Errorf("expected %d error definitions but got %d ", 1, len(e.Errors))
					}
					if e.Description != "" {
						t.Errorf("no endpoint Description was defined expected an empty Description but got %s", e.Description)
					}
					if len(e.Metadata) != 0 {
						t.Errorf("no endpoint Metadata defined expected an empty Metadata but got %v ", e.Metadata)
					}
					assertEndpointError(t, e.Errors[0], "basic_media_error", design.ErrorMedia)
					expectedReq := &design.UserTypeExpr{TypeName: "AnotherRequest", AttributeExpr: &design.AttributeExpr{Type: design.String}}
					assertEndpointRequestResponse(t, "Request", e.Request, expectedReq)
					expectedRes := &design.UserTypeExpr{TypeName: "AnotherResponse", AttributeExpr: &design.AttributeExpr{Type: design.String}}
					assertEndpointRequestResponse(t, "Response", e.Response, expectedRes)
				},
			},
		},
	}
	//Run our tests
	for k, tc := range cases {
		t.Run(k, func(t *testing.T) {
			eval.Context = &eval.DSLContext{}
			eval.Execute(tc.DSL, tc.Expr)
			evalService := tc.Expr.(*design.ServiceExpr)
			if eval.Context.Errors != nil {
				t.Errorf("%s: Endpoint failed unexpectedly with %s", k, eval.Context.Errors)
			}
			for _, e := range evalService.Endpoints {
				if _, ok := tc.Assert[e.Name]; !ok {
					t.Errorf("no assert found for endpoint %s ", e.Name)
					break
				}
				tc.Assert[e.Name](t, e)
			}
		})
	}
}

//helper funcs
func assertEndpointDocs(t *testing.T, doc *design.DocsExpr, url, desc string) {
	if doc.Description != desc {
		t.Errorf("expected docs description '%s' to match '%s' ", desc, doc.Description)
	}
	if doc.URL != url {
		t.Errorf("expected docs url '%s' to match '%s' ", url, doc.URL)
	}
}

func assertEndpointDescription(t *testing.T, expectedDesc, actualDesc string) {
	if expectedDesc != actualDesc {
		t.Errorf("expected description '%s' to match '%s' ", actualDesc, expectedDesc)
	}
}

func assertEndpointError(t *testing.T, actual *design.ErrorExpr, name string, dt design.DataType) {
	if actual.Name != name {
		t.Errorf("expected error to have name %s but got %s ", name, actual.Name)
	}

	if actual.AttributeExpr.Type != dt {
		t.Errorf("expected the error DataType to be %v but got %v ", dt, actual.AttributeExpr.Type)
	}
}

func assertEndpointMetaData(t *testing.T, actual design.MetadataExpr, expected design.MetadataExpr) {
	for key, val := range actual {
		vals, ok := expected[key]
		if !ok {
			t.Errorf("metaData was missing expected key %s ", key)
			continue
		}
		for _, metaVal := range val {
			if !hasValue(vals, metaVal) {
				t.Errorf("metaData was missing expected value %s ", metaVal)
			}
		}

	}
}

func assertEndpointRequestResponse(t *testing.T, assertType string, actual design.DataType, expected *design.UserTypeExpr) {
	ut, ok := actual.(*design.UserTypeExpr)
	if !ok || ut == nil {
		t.Errorf("expected endpoint %s to be a *UserTypeExpr but got %v", assertType, reflect.TypeOf(ut))
		return
	}
	if ut.Name() != expected.Name() {
		t.Errorf("expected endpoint %s name %s to match %s", assertType, ut.Name(), expected.Name())
	}
	if ut.AttributeExpr.Type.Name() != expected.Type.Name() {
		t.Errorf("expected endpoint %s TypeName %s to match %s ", assertType, ut.Type.Name(), expected.Type.Name())
	}
	if ut.Description != expected.Description {
		t.Errorf("expected endpoint %s description %s to match %s", assertType, ut.Description, expected.Description)
	}

}
<|endoftext|>


<commit_before>/* Copyright 2016 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gonids

import (
	"bytes"
	"strings"
)

// ShouldBeHTTP returns true if a rule looks like the protocol should be http, but is not.
func (r *Rule) ShouldBeHTTP() bool {
	// If the rule is already HTTP, then stop looking.
	if r.Protocol == "http" {
		return false
	}
	// If we look at http buffers or sticky buffers, we should use the HTTP protocol.
	for _, c := range r.Contents() {
		if strings.HasPrefix(c.DataPosition.String(), "http_") {
			return true
		}
		for _, co := range c.Options {
			if strings.HasPrefix(co.Name, "http_") {
				return true
			}
		}
	}
	return false
}

// TODO: See if ET folks have any data around this.
// Minimum length of a content to be considered safe for use with a PCRE.
const minPCREContentLen = 5

// Some of these may be caught by min length check, but including for completeness.
// All lower case for case insenstive checks.
// Many of this come from: https://github.com/EmergingThreats/IDSDeathBlossom/blob/master/config/fpblacklist.txt
var bannedContents = []string{"get",
	"post",
	"/",
	"user-agent",
	"user-agent: mozilla",
	"host",
	"index.php",
	"index.php?id=",
	"index.html",
	"content-length",
	".htm",
	".html",
	".php",
	".asp",
	".aspx",
	"content-disposition",
	"wp-content/plugins",
	"wp-content/themes",
	"activexobject",
	"default.asp",
	"default.aspx",
	"default.asp",
}

// ExpensivePCRE returns true if a rule appears to use a PCRE without
// conditions that make it expensive to compute.
func (r *Rule) ExpensivePCRE() bool {
	// No PCRE, not expensive.
	if len(r.PCREs()) < 1 {
		return false
	}

	// If we have PCRE, but no contents, this is probably expensive.
	cs := r.Contents()
	if len(cs) < 1 {
		return true
	}

	// Look for a content with sufficient length to make performance acceptable.
	short := true
	for _, c := range cs {
		// TODO: Identify a sane length.
		if len(c.Pattern) >= minPCREContentLen {
			short = false
		}
	}
	if short {
		return true
	}

	// If all content matches are common strings, also not good.
	common := true
	for _, c := range cs {
		if !inSlice(strings.ToLower(strings.Trim(string(c.Pattern), "\r\n :/?")), bannedContents) {
			return false
		}
	}
	if common {
		return true
	}

	// Don't flag a rule if we haven't defined a condition that's interesting.
	return false
}

// SnortHTTPHeader returns true if any content contains double CRLF at the end.
func (r *Rule) SnortHTTPHeader() bool {
	cs := r.Contents()
	if len(cs) < 1 {
		return false
	}
	for _, c := range cs {
		if c.SnortHTTPHeader() {
			return true
		}
	}
	return false
}

// SnortHTTPHeader returns true if a specific content contains double CRLF at the end.
func (c Content) SnortHTTPHeader() bool {
	for _, o := range c.Options {
		if o.Name == "http_header" {
			if bytes.HasSuffix(c.Pattern, []byte("\r\n\r\n")) {
				return true
			}
		}
	}
	return false
}

// NoReferences returns true if there are no references in the rule.
func (r *Rule) NoReferences() bool {
	if len(r.References) == 0 {
		return true
	}
	return false
}

// Length at which we warn if all matchers are this Contents with length or shorter.
// Possibly align this with the minPCREContentLength.
const shortContentLen = 4

// OnlyShortContents returns true if all content matches are very short.
func (r *Rule) OnlyShortContents() bool {
	// There are non-Content matches in the rule.
	cs := r.Contents()
	if len(r.Matchers) != len(cs) {
		return false
	}
	for _, c := range cs {
		// Some content is longer than the minimum.
		if len(c.Pattern) > shortContentLen {
			return false
		}
	}
	return true
}
<commit_msg>Update function description to me more accurate.<commit_after>/* Copyright 2016 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gonids

import (
	"bytes"
	"strings"
)

// ShouldBeHTTP returns true if a rule looks like the protocol should be http, but is not.
func (r *Rule) ShouldBeHTTP() bool {
	// If the rule is already HTTP, then stop looking.
	if r.Protocol == "http" {
		return false
	}
	// If we look at http buffers or sticky buffers, we should use the HTTP protocol.
	for _, c := range r.Contents() {
		if strings.HasPrefix(c.DataPosition.String(), "http_") {
			return true
		}
		for _, co := range c.Options {
			if strings.HasPrefix(co.Name, "http_") {
				return true
			}
		}
	}
	return false
}

// TODO: See if ET folks have any data around this.
// Minimum length of a content to be considered safe for use with a PCRE.
const minPCREContentLen = 5

// Some of these may be caught by min length check, but including for completeness.
// All lower case for case insenstive checks.
// Many of this come from: https://github.com/EmergingThreats/IDSDeathBlossom/blob/master/config/fpblacklist.txt
var bannedContents = []string{"get",
	"post",
	"/",
	"user-agent",
	"user-agent: mozilla",
	"host",
	"index.php",
	"index.php?id=",
	"index.html",
	"content-length",
	".htm",
	".html",
	".php",
	".asp",
	".aspx",
	"content-disposition",
	"wp-content/plugins",
	"wp-content/themes",
	"activexobject",
	"default.asp",
	"default.aspx",
	"default.asp",
}

// ExpensivePCRE returns true if a rule appears to use a PCRE without
// conditions that make it expensive to compute.
func (r *Rule) ExpensivePCRE() bool {
	// No PCRE, not expensive.
	if len(r.PCREs()) < 1 {
		return false
	}

	// If we have PCRE, but no contents, this is probably expensive.
	cs := r.Contents()
	if len(cs) < 1 {
		return true
	}

	// Look for a content with sufficient length to make performance acceptable.
	short := true
	for _, c := range cs {
		// TODO: Identify a sane length.
		if len(c.Pattern) >= minPCREContentLen {
			short = false
		}
	}
	if short {
		return true
	}

	// If all content matches are common strings, also not good.
	common := true
	for _, c := range cs {
		if !inSlice(strings.ToLower(strings.Trim(string(c.Pattern), "\r\n :/?")), bannedContents) {
			return false
		}
	}
	if common {
		return true
	}

	// Don't flag a rule if we haven't defined a condition that's interesting.
	return false
}

// SnortHTTPHeader returns true if any content contains double CRLF at the end.
func (r *Rule) SnortHTTPHeader() bool {
	cs := r.Contents()
	if len(cs) < 1 {
		return false
	}
	for _, c := range cs {
		if c.SnortHTTPHeader() {
			return true
		}
	}
	return false
}

// SnortHTTPHeader returns true if a specific content contains double CRLF at the end.
func (c Content) SnortHTTPHeader() bool {
	for _, o := range c.Options {
		if o.Name == "http_header" {
			if bytes.HasSuffix(c.Pattern, []byte("\r\n\r\n")) {
				return true
			}
		}
	}
	return false
}

// NoReferences returns true if there are no references in the rule.
func (r *Rule) NoReferences() bool {
	if len(r.References) == 0 {
		return true
	}
	return false
}

// Length at which we warn if all matchers are this Contents with length or shorter.
// Possibly align this with the minPCREContentLength.
const shortContentLen = 4

// OnlyShortContents returns true if all Matchers are Contents and all matches are very short.
func (r *Rule) OnlyShortContents() bool {
	// There are non-Content matches in the rule.
	cs := r.Contents()
	if len(r.Matchers) != len(cs) {
		return false
	}
	for _, c := range cs {
		// Some content is longer than the minimum.
		if len(c.Pattern) > shortContentLen {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package log

import "log"

var Target *log.Logger

func Fatal(v ...interface{}) {
	if Target != nil {
		Target.Fatal(v...)
	}
}

func Print(v ...interface{}) {
	if Target != nil {
		Target.Print(v...)
	}
}

func Printf(format string, v ...interface{}) {
	if Target != nil {
		Target.Printf(format, v...)
	}
}

func Panic(v ...interface{}) {
	if Target != nil {
		Target.Panic(v...)
	}
}

func Panicf(format string, v ...interface{}) {
	if Target != nil {
		Target.Panicf(format, v...)
	}
}
<commit_msg>log: added some docs to the package<commit_after>// Package log provides logging utility.
//
// It abstracts the Logger from the standard log package, allowing the
// developer to patck the logging target, changing this to a file, or syslog,
// for example.
package log

import "log"

// Target is the current target for the log package.
var Target *log.Logger

// Fatal is equivalent to Print() followed by os.Exit(1).
func Fatal(v ...interface{}) {
	if Target != nil {
		Target.Fatal(v...)
	}
}

// Print is similar to fmt.Print, writing the given values to the Target
// logger.
func Print(v ...interface{}) {
	if Target != nil {
		Target.Print(v...)
	}
}

// Printf is similar to fmt.Printf, writing the formatted string to the Target
// logger.
func Printf(format string, v ...interface{}) {
	if Target != nil {
		Target.Printf(format, v...)
	}
}

// Panic is equivalent to Print() followed by panic().
func Panic(v ...interface{}) {
	if Target != nil {
		Target.Panic(v...)
	}
}

// Panicf is equivalent to Printf() followed by panic().
func Panicf(format string, v ...interface{}) {
	if Target != nil {
		Target.Panicf(format, v...)
	}
}
<|endoftext|>


<commit_before>package mvt

import (
	"errors"
	"fmt"
	"log"

	vt "github.com/thomersch/grandine/lib/mvt/vector_tile"
	"github.com/thomersch/grandine/lib/spatial"
	"github.com/thomersch/grandine/lib/tile"

	"github.com/golang/protobuf/proto"
)

type cmd uint32

const (
	cmdMoveTo    cmd = 1
	cmdLineTo    cmd = 2
	cmdClosePath cmd = 7

	extent = 4096
)

var (
	vtPoint        = vt.Tile_POINT
	vtLine         = vt.Tile_LINESTRING
	vtPoly         = vt.Tile_POLYGON
	vtLayerVersion = uint32(2)

	skipAtKeys = true // if enabled keys that start with "@" will be ignored
)

func encodeCommandInt(c cmd, count uint32) uint32 {
	return (uint32(c) & 0x7) | (count << 3)
}

func decodeCommandInt(cmdInt uint32) (cmd, uint32) {
	return cmd(cmdInt & 0x7), cmdInt >> 3
}

func encodeZigZag(i int) uint32 {
	return uint32((i << 1) ^ (i >> 31))
}

func EncodeTile(features map[string][]spatial.Feature, tid tile.ID) ([]byte, error) {
	vtile, err := assembleTile(features, tid)
	if err != nil {
		return nil, err
	}
	return proto.Marshal(&vtile)
}

func assembleTile(features map[string][]spatial.Feature, tid tile.ID) (vt.Tile, error) {
	var vtile vt.Tile
	for layerName, layerFeats := range features {
		layer, err := assembleLayer(layerFeats, tid)
		if err != nil {
			return vtile, err
		}
		layer.Name = &layerName
		layer.Version = &vtLayerVersion
		vtile.Layers = append(vtile.Layers, &layer)
	}
	return vtile, nil
}

// tagElems is an intermediate data structure for serializing keys or values into flat
// index referenced lists as used by MVT
type tagElems map[interface{}]int

func (te tagElems) Index(v interface{}) int {
	if pos, ok := te[v]; ok {
		return pos
	}
	pos := len(te)
	te[v] = pos
	return pos
}

func (te tagElems) Strings() []string {
	var l = make([]string, len(te))
	for elem, pos := range te {
		l[pos] = elem.(string)
	}
	return l
}

func (te tagElems) Values() []*vt.Tile_Value {
	var l = make([]*vt.Tile_Value, len(te))
	for val, pos := range te {
		var tv vt.Tile_Value
		switch v := val.(type) {
		case string:
			tv.StringValue = &v
		case float32:
			tv.FloatValue = &v
		case float64:
			tv.DoubleValue = &v
		case int:
			i := int64(v)
			tv.SintValue = &i
		case int64:
			tv.SintValue = &v
		case uint:
			i := uint64(v)
			tv.UintValue = &i
		case uint64:
			tv.UintValue = &v
		case bool:
			tv.BoolValue = &v
		default:
			s := fmt.Sprintf("%s", v)
			tv.StringValue = &s
		}
		l[pos] = &tv
	}
	return l
}

func assembleLayer(features []spatial.Feature, tid tile.ID) (vt.Tile_Layer, error) {
	var (
		tl   vt.Tile_Layer
		err  error
		ext  = uint32(4096)
		keys = tagElems{}
		vals = tagElems{}
	)

	for _, feat := range features {
		var tileFeat vt.Tile_Feature

		for k, v := range feat.Properties() {
			if skipAtKeys && k[0] == '@' {
				continue
			}
			kpos := keys.Index(k)
			vpos := vals.Index(v)
			tileFeat.Tags = append(tileFeat.Tags, uint32(kpos), uint32(vpos))
		}

		tileFeat.Geometry, err = encodeGeometry([]spatial.Geom{feat.Geometry}, tid)
		if err != nil {
			return tl, err
		}
		switch feat.Geometry.Typ() {
		case spatial.GeomTypePoint:
			tileFeat.Type = &vtPoint
		case spatial.GeomTypeLineString:
			tileFeat.Type = &vtLine
		case spatial.GeomTypePolygon:
			tileFeat.Type = &vtPoly
		default:
			return tl, errors.New("unknown geometry type")
		}

		tl.Features = append(tl.Features, &tileFeat)
		tl.Extent = &ext //TODO: configurable?
	}

	tl.Keys = keys.Strings()
	tl.Values = vals.Values()
	return tl, nil
}

// encodes one or more geometries of the same type into one (multi-)geometry
func encodeGeometry(geoms []spatial.Geom, tid tile.ID) (commands []uint32, err error) {
	var (
		cur    [2]int
		dx, dy int
		// the following four lines might be optimized
		bbox             = tid.BBox()
		xScale, yScale   = tileScalingFactor(bbox, extent)
		xOffset, yOffset = tileOffset(bbox)
	)
	var typ spatial.GeomType
	for _, geom := range geoms {
		if typ != 0 && typ != geom.Typ() {
			return nil, errors.New("encodeGeometry only accepts uniform geoms")
		}

		cur[0] = 0
		cur[1] = 0
		switch geom.Typ() {
		case spatial.GeomTypePoint:
			pt, _ := geom.Point()
			tX, tY := tileCoord(pt, extent, xScale, yScale, xOffset, yOffset)
			if tX > extent || tY > extent {
				log.Printf("%v is outside of tile", pt)
			}
			dx = tX - int(cur[0])
			dy = tY - int(cur[1])
			// TODO: support multipoint
			commands = append(commands, encodeCommandInt(cmdMoveTo, 1), encodeZigZag(dx), encodeZigZag(dy))
		case spatial.GeomTypeLineString:
			ln, _ := geom.LineString()
			commands = append(commands, encodeLine(ln, cur, extent, xScale, yScale, xOffset, yOffset)...)
		case spatial.GeomTypePolygon:
			// TODO: inner ring handling
			poly, _ := geom.Polygon()
			commands = append(commands, encodeLine(poly[0], cur, extent, xScale, yScale, xOffset, yOffset)...)
			commands = append(commands, encodeCommandInt(cmdClosePath, 1))
		}
	}
	return commands, nil
}

func encodeLine(ln spatial.Line, cur [2]int, extent int, xScale, yScale, xOffset, yOffset float64) (commands []uint32) {
	tX, tY := tileCoord(ln[0], extent, xScale, yScale, xOffset, yOffset)
	dx := tX - int(cur[0])
	dy := tY - int(cur[1])
	cur[0] = cur[0] + dx
	cur[1] = cur[1] + dy

	commands = append(commands, encodeCommandInt(cmdMoveTo, 1), encodeZigZag(dx), encodeZigZag(dy),
		encodeCommandInt(cmdLineTo, uint32(len(ln)-1)))

	for _, pt := range ln[1:] {
		tX, tY = tileCoord(pt, extent, xScale, yScale, xOffset, yOffset)
		dx = tX - int(cur[0])
		dy = tY - int(cur[1])
		commands = append(commands, encodeZigZag(dx), encodeZigZag(dy))
		cur[0] = cur[0] + dx
		cur[1] = cur[1] + dy
	}
	return commands
}
<commit_msg>lib/mvt: layer name fixed<commit_after>package mvt

import (
	"errors"
	"fmt"
	"log"

	vt "github.com/thomersch/grandine/lib/mvt/vector_tile"
	"github.com/thomersch/grandine/lib/spatial"
	"github.com/thomersch/grandine/lib/tile"

	"github.com/golang/protobuf/proto"
)

type cmd uint32

const (
	cmdMoveTo    cmd = 1
	cmdLineTo    cmd = 2
	cmdClosePath cmd = 7

	extent = 4096
)

var (
	vtPoint        = vt.Tile_POINT
	vtLine         = vt.Tile_LINESTRING
	vtPoly         = vt.Tile_POLYGON
	vtLayerVersion = uint32(2)

	skipAtKeys = true // if enabled keys that start with "@" will be ignored
)

func encodeCommandInt(c cmd, count uint32) uint32 {
	return (uint32(c) & 0x7) | (count << 3)
}

func decodeCommandInt(cmdInt uint32) (cmd, uint32) {
	return cmd(cmdInt & 0x7), cmdInt >> 3
}

func encodeZigZag(i int) uint32 {
	return uint32((i << 1) ^ (i >> 31))
}

func EncodeTile(features map[string][]spatial.Feature, tid tile.ID) ([]byte, error) {
	vtile, err := assembleTile(features, tid)
	if err != nil {
		return nil, err
	}
	return proto.Marshal(&vtile)
}

func assembleTile(features map[string][]spatial.Feature, tid tile.ID) (vt.Tile, error) {
	var vtile vt.Tile
	for layerName, layerFeats := range features {
		layer, err := assembleLayer(layerFeats, tid)
		if err != nil {
			return vtile, err
		}
		var ln = layerName
		layer.Name = &ln // &layerName can't be used directly, because pointers are reused in for range loops
		layer.Version = &vtLayerVersion
		vtile.Layers = append(vtile.Layers, &layer)
	}
	return vtile, nil
}

// tagElems is an intermediate data structure for serializing keys or values into flat
// index referenced lists as used by MVT
type tagElems map[interface{}]int

func (te tagElems) Index(v interface{}) int {
	if pos, ok := te[v]; ok {
		return pos
	}
	pos := len(te)
	te[v] = pos
	return pos
}

func (te tagElems) Strings() []string {
	var l = make([]string, len(te))
	for elem, pos := range te {
		l[pos] = elem.(string)
	}
	return l
}

func (te tagElems) Values() []*vt.Tile_Value {
	var l = make([]*vt.Tile_Value, len(te))
	for val, pos := range te {
		var tv vt.Tile_Value
		switch v := val.(type) {
		case string:
			tv.StringValue = &v
		case float32:
			tv.FloatValue = &v
		case float64:
			tv.DoubleValue = &v
		case int:
			i := int64(v)
			tv.SintValue = &i
		case int64:
			tv.SintValue = &v
		case uint:
			i := uint64(v)
			tv.UintValue = &i
		case uint64:
			tv.UintValue = &v
		case bool:
			tv.BoolValue = &v
		default:
			s := fmt.Sprintf("%s", v)
			tv.StringValue = &s
		}
		l[pos] = &tv
	}
	return l
}

func assembleLayer(features []spatial.Feature, tid tile.ID) (vt.Tile_Layer, error) {
	var (
		tl   vt.Tile_Layer
		err  error
		ext  = uint32(4096)
		keys = tagElems{}
		vals = tagElems{}
	)

	for _, feat := range features {
		var tileFeat vt.Tile_Feature

		for k, v := range feat.Properties() {
			if skipAtKeys && k[0] == '@' {
				continue
			}
			kpos := keys.Index(k)
			vpos := vals.Index(v)
			tileFeat.Tags = append(tileFeat.Tags, uint32(kpos), uint32(vpos))
		}

		tileFeat.Geometry, err = encodeGeometry([]spatial.Geom{feat.Geometry}, tid)
		if err != nil {
			return tl, err
		}
		switch feat.Geometry.Typ() {
		case spatial.GeomTypePoint:
			tileFeat.Type = &vtPoint
		case spatial.GeomTypeLineString:
			tileFeat.Type = &vtLine
		case spatial.GeomTypePolygon:
			tileFeat.Type = &vtPoly
		default:
			return tl, errors.New("unknown geometry type")
		}

		tl.Features = append(tl.Features, &tileFeat)
		tl.Extent = &ext //TODO: configurable?
	}

	tl.Keys = keys.Strings()
	tl.Values = vals.Values()
	return tl, nil
}

// encodes one or more geometries of the same type into one (multi-)geometry
func encodeGeometry(geoms []spatial.Geom, tid tile.ID) (commands []uint32, err error) {
	var (
		cur    [2]int
		dx, dy int
		// the following four lines might be optimized
		bbox             = tid.BBox()
		xScale, yScale   = tileScalingFactor(bbox, extent)
		xOffset, yOffset = tileOffset(bbox)
	)
	var typ spatial.GeomType
	for _, geom := range geoms {
		if typ != 0 && typ != geom.Typ() {
			return nil, errors.New("encodeGeometry only accepts uniform geoms")
		}

		cur[0] = 0
		cur[1] = 0
		switch geom.Typ() {
		case spatial.GeomTypePoint:
			pt, _ := geom.Point()
			tX, tY := tileCoord(pt, extent, xScale, yScale, xOffset, yOffset)
			if tX > extent || tY > extent {
				log.Printf("%v is outside of tile", pt)
			}
			dx = tX - int(cur[0])
			dy = tY - int(cur[1])
			// TODO: support multipoint
			commands = append(commands, encodeCommandInt(cmdMoveTo, 1), encodeZigZag(dx), encodeZigZag(dy))
		case spatial.GeomTypeLineString:
			ln, _ := geom.LineString()
			commands = append(commands, encodeLine(ln, cur, extent, xScale, yScale, xOffset, yOffset)...)
		case spatial.GeomTypePolygon:
			// TODO: inner ring handling
			poly, _ := geom.Polygon()
			commands = append(commands, encodeLine(poly[0], cur, extent, xScale, yScale, xOffset, yOffset)...)
			commands = append(commands, encodeCommandInt(cmdClosePath, 1))
		}
	}
	return commands, nil
}

func encodeLine(ln spatial.Line, cur [2]int, extent int, xScale, yScale, xOffset, yOffset float64) (commands []uint32) {
	tX, tY := tileCoord(ln[0], extent, xScale, yScale, xOffset, yOffset)
	dx := tX - int(cur[0])
	dy := tY - int(cur[1])
	cur[0] = cur[0] + dx
	cur[1] = cur[1] + dy

	commands = append(commands, encodeCommandInt(cmdMoveTo, 1), encodeZigZag(dx), encodeZigZag(dy),
		encodeCommandInt(cmdLineTo, uint32(len(ln)-1)))

	for _, pt := range ln[1:] {
		tX, tY = tileCoord(pt, extent, xScale, yScale, xOffset, yOffset)
		dx = tX - int(cur[0])
		dy = tY - int(cur[1])
		commands = append(commands, encodeZigZag(dx), encodeZigZag(dy))
		cur[0] = cur[0] + dx
		cur[1] = cur[1] + dy
	}
	return commands
}
<|endoftext|>


<commit_before>// Copyright 2013 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 app

import (
	"encoding/json"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/tsuru/api/shutdown"
	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/log"
	"github.com/tsuru/tsuru/queue"
)

var (
	LogPubSubQueuePrefix = "pubsub:"

	bulkMaxWaitTime   = time.Second
	bulkMaxNumberMsgs = 500

	logsInQueue = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "tsuru_logs_queue_current",
		Help: "The current number of log entries in all queues.",
	})

	logsQueueBlockedTotal = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "tsuru_logs_queue_blocked_seconds_total",
		Help: "The total time spent blocked trying to add log to queue.",
	})

	logsQueueSize = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "tsuru_logs_dispatcher_queue_size",
		Help: "The max number of log entries in a dispatcher queue.",
	})

	logsWritten = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "tsuru_logs_write_total",
		Help: "The number of log entries written to mongo.",
	})
)

func init() {
	prometheus.MustRegister(logsInQueue)
	prometheus.MustRegister(logsQueueSize)
	prometheus.MustRegister(logsWritten)
	prometheus.MustRegister(logsQueueBlockedTotal)
}

type LogListener struct {
	c    <-chan Applog
	q    queue.PubSubQ
	quit chan struct{}
}

func logQueueName(appName string) string {
	return LogPubSubQueuePrefix + appName
}

func NewLogListener(a *App, filterLog Applog) (*LogListener, error) {
	factory, err := queue.Factory()
	if err != nil {
		return nil, err
	}
	pubSubQ, err := factory.PubSub(logQueueName(a.Name))
	if err != nil {
		return nil, err
	}
	subChan, err := pubSubQ.Sub()
	if err != nil {
		return nil, err
	}
	c := make(chan Applog, 10)
	quit := make(chan struct{})
	go func() {
		defer close(c)
		for {
			var msg []byte
			select {
			case msg = <-subChan:
			case <-quit:
				return
			}
			applog := Applog{}
			err := json.Unmarshal(msg, &applog)
			if err != nil {
				log.Errorf("Unparsable log message, ignoring: %s", string(msg))
				continue
			}
			if (filterLog.Source == "" || filterLog.Source == applog.Source) &&
				(filterLog.Unit == "" || filterLog.Unit == applog.Unit) {
				select {
				case c <- applog:
				case <-quit:
					return
				}
			}
		}
	}()
	l := LogListener{c: c, q: pubSubQ, quit: quit}
	return &l, nil
}

func (l *LogListener) ListenChan() <-chan Applog {
	return l.c
}

func (l *LogListener) Close() (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = errors.Errorf("Recovered panic closing listener (possible double close): %v", r)
		}
	}()
	close(l.quit)
	err = l.q.UnSub()
	return
}

func notify(appName string, messages []interface{}) {
	factory, err := queue.Factory()
	if err != nil {
		log.Errorf("Error on logs notify: %s", err)
		return
	}
	pubSubQ, err := factory.PubSub(logQueueName(appName))
	if err != nil {
		log.Errorf("Error on logs notify: %s", err)
		return
	}
	for _, msg := range messages {
		bytes, err := json.Marshal(msg)
		if err != nil {
			log.Errorf("Error on logs notify: %s", err)
			continue
		}
		err = pubSubQ.Pub(bytes)
		if err != nil {
			log.Errorf("Error on logs notify: %s", err)
		}
	}
}

type LogDispatcher struct {
	mu             sync.RWMutex
	sendMu         sync.Mutex
	dispatchers    map[string]*appLogDispatcher
	msgCh          chan *Applog
	shuttingDown   int32
	doneProcessing chan struct{}
}

func NewlogDispatcher(chanSize int) *LogDispatcher {
	d := &LogDispatcher{
		dispatchers:    make(map[string]*appLogDispatcher),
		msgCh:          make(chan *Applog, chanSize),
		doneProcessing: make(chan struct{}),
	}
	go d.runWriter()
	shutdown.Register(d)
	logsQueueSize.Set(float64(chanSize))
	return d
}

func (d *LogDispatcher) getMessageDispatcher(msg *Applog) *appLogDispatcher {
	appName := msg.AppName
	d.mu.RLock()
	appD, ok := d.dispatchers[appName]
	if !ok {
		d.mu.RUnlock()
		d.mu.Lock()
		appD, ok = d.dispatchers[appName]
		if !ok {
			appD = newAppLogDispatcher(appName)
			d.dispatchers[appName] = appD
		}
		d.mu.Unlock()
	} else {
		d.mu.RUnlock()
	}
	return appD
}

func (d *LogDispatcher) runWriter() {
	defer close(d.doneProcessing)
	notifyMessages := make([]interface{}, 1)
	for msg := range d.msgCh {
		if msg == nil {
			break
		}
		logsInQueue.Dec()
		appD := d.getMessageDispatcher(msg)
		notifyMessages[0] = msg
		notify(msg.AppName, notifyMessages)
		appD.toFlush <- msg
	}
}

func (d *LogDispatcher) Send(msg *Applog) error {
	if atomic.LoadInt32(&d.shuttingDown) == 1 {
		return errors.New("log dispatcher is shutting down")
	}
	logsInQueue.Inc()
	select {
	case d.msgCh <- msg:
	default:
		t0 := time.Now()
		d.msgCh <- msg
		logsQueueBlockedTotal.Add(time.Since(t0).Seconds())
	}
	return nil
}

func (a *LogDispatcher) String() string {
	return "log dispatcher"
}

func (d *LogDispatcher) Shutdown() {
	atomic.StoreInt32(&d.shuttingDown, 1)
	d.msgCh <- nil
	<-d.doneProcessing
	logsInQueue.Set(0)
	for _, appD := range d.dispatchers {
		close(appD.done)
		<-appD.finished
	}
}

type appLogDispatcher struct {
	appName  string
	done     chan struct{}
	finished chan struct{}
	toFlush  chan *Applog
}

func newAppLogDispatcher(appName string) *appLogDispatcher {
	d := &appLogDispatcher{
		appName:  appName,
		done:     make(chan struct{}),
		finished: make(chan struct{}),
		toFlush:  make(chan *Applog),
	}
	go d.runFlusher()
	return d
}

func (d *appLogDispatcher) runFlusher() {
	defer close(d.finished)
	t := time.NewTimer(bulkMaxWaitTime)
	pos := 0
	bulkBuffer := make([]interface{}, bulkMaxNumberMsgs)
	shouldReturn := false
	for {
		var flush bool
		select {
		case <-d.done:
			flush = pos > 0
			shouldReturn = true
		case msg := <-d.toFlush:
			if pos == bulkMaxNumberMsgs {
				flush = true
				break
			}
			bulkBuffer[pos] = msg
			pos++
			flush = bulkMaxNumberMsgs == pos
		case <-t.C:
			flush = pos > 0
			t.Reset(bulkMaxWaitTime)
		}
		if flush {
			conn, err := db.LogConn()
			if err != nil {
				log.Errorf("[log flusher] unable to connect to mongodb: %s", err)
				continue
			}
			coll := conn.Logs(d.appName)
			err = coll.Insert(bulkBuffer[:pos]...)
			coll.Close()
			if err != nil {
				log.Errorf("[log flusher] unable to insert logs: %s", err)
				continue
			}
			logsWritten.Add(float64(pos))
			pos = 0
		}
		if shouldReturn {
			return
		}
	}
}
<commit_msg>app: add counter for number of enqueued messages<commit_after>// Copyright 2013 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 app

import (
	"encoding/json"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/tsuru/api/shutdown"
	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/log"
	"github.com/tsuru/tsuru/queue"
)

var (
	LogPubSubQueuePrefix = "pubsub:"

	bulkMaxWaitTime   = time.Second
	bulkMaxNumberMsgs = 500

	logsInQueue = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "tsuru_logs_queue_current",
		Help: "The current number of log entries in all queues.",
	})

	logsQueueBlockedTotal = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "tsuru_logs_queue_blocked_seconds_total",
		Help: "The total time spent blocked trying to add log to queue.",
	})

	logsQueueSize = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "tsuru_logs_dispatcher_queue_size",
		Help: "The max number of log entries in a dispatcher queue.",
	})

	logsEnqueued = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "tsuru_logs_enqueued_total",
		Help: "The number of log entries enqueued for processing.",
	})

	logsWritten = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "tsuru_logs_write_total",
		Help: "The number of log entries written to mongo.",
	})
)

func init() {
	prometheus.MustRegister(logsInQueue)
	prometheus.MustRegister(logsQueueSize)
	prometheus.MustRegister(logsEnqueued)
	prometheus.MustRegister(logsWritten)
	prometheus.MustRegister(logsQueueBlockedTotal)
}

type LogListener struct {
	c    <-chan Applog
	q    queue.PubSubQ
	quit chan struct{}
}

func logQueueName(appName string) string {
	return LogPubSubQueuePrefix + appName
}

func NewLogListener(a *App, filterLog Applog) (*LogListener, error) {
	factory, err := queue.Factory()
	if err != nil {
		return nil, err
	}
	pubSubQ, err := factory.PubSub(logQueueName(a.Name))
	if err != nil {
		return nil, err
	}
	subChan, err := pubSubQ.Sub()
	if err != nil {
		return nil, err
	}
	c := make(chan Applog, 10)
	quit := make(chan struct{})
	go func() {
		defer close(c)
		for {
			var msg []byte
			select {
			case msg = <-subChan:
			case <-quit:
				return
			}
			applog := Applog{}
			err := json.Unmarshal(msg, &applog)
			if err != nil {
				log.Errorf("Unparsable log message, ignoring: %s", string(msg))
				continue
			}
			if (filterLog.Source == "" || filterLog.Source == applog.Source) &&
				(filterLog.Unit == "" || filterLog.Unit == applog.Unit) {
				select {
				case c <- applog:
				case <-quit:
					return
				}
			}
		}
	}()
	l := LogListener{c: c, q: pubSubQ, quit: quit}
	return &l, nil
}

func (l *LogListener) ListenChan() <-chan Applog {
	return l.c
}

func (l *LogListener) Close() (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = errors.Errorf("Recovered panic closing listener (possible double close): %v", r)
		}
	}()
	close(l.quit)
	err = l.q.UnSub()
	return
}

func notify(appName string, messages []interface{}) {
	factory, err := queue.Factory()
	if err != nil {
		log.Errorf("Error on logs notify: %s", err)
		return
	}
	pubSubQ, err := factory.PubSub(logQueueName(appName))
	if err != nil {
		log.Errorf("Error on logs notify: %s", err)
		return
	}
	for _, msg := range messages {
		bytes, err := json.Marshal(msg)
		if err != nil {
			log.Errorf("Error on logs notify: %s", err)
			continue
		}
		err = pubSubQ.Pub(bytes)
		if err != nil {
			log.Errorf("Error on logs notify: %s", err)
		}
	}
}

type LogDispatcher struct {
	mu             sync.RWMutex
	sendMu         sync.Mutex
	dispatchers    map[string]*appLogDispatcher
	msgCh          chan *Applog
	shuttingDown   int32
	doneProcessing chan struct{}
}

func NewlogDispatcher(chanSize int) *LogDispatcher {
	d := &LogDispatcher{
		dispatchers:    make(map[string]*appLogDispatcher),
		msgCh:          make(chan *Applog, chanSize),
		doneProcessing: make(chan struct{}),
	}
	go d.runWriter()
	shutdown.Register(d)
	logsQueueSize.Set(float64(chanSize))
	return d
}

func (d *LogDispatcher) getMessageDispatcher(msg *Applog) *appLogDispatcher {
	appName := msg.AppName
	d.mu.RLock()
	appD, ok := d.dispatchers[appName]
	if !ok {
		d.mu.RUnlock()
		d.mu.Lock()
		appD, ok = d.dispatchers[appName]
		if !ok {
			appD = newAppLogDispatcher(appName)
			d.dispatchers[appName] = appD
		}
		d.mu.Unlock()
	} else {
		d.mu.RUnlock()
	}
	return appD
}

func (d *LogDispatcher) runWriter() {
	defer close(d.doneProcessing)
	notifyMessages := make([]interface{}, 1)
	for msg := range d.msgCh {
		if msg == nil {
			break
		}
		logsInQueue.Dec()
		appD := d.getMessageDispatcher(msg)
		notifyMessages[0] = msg
		notify(msg.AppName, notifyMessages)
		appD.toFlush <- msg
	}
}

func (d *LogDispatcher) Send(msg *Applog) error {
	if atomic.LoadInt32(&d.shuttingDown) == 1 {
		return errors.New("log dispatcher is shutting down")
	}
	logsInQueue.Inc()
	logsEnqueued.Inc()
	select {
	case d.msgCh <- msg:
	default:
		t0 := time.Now()
		d.msgCh <- msg
		logsQueueBlockedTotal.Add(time.Since(t0).Seconds())
	}
	return nil
}

func (a *LogDispatcher) String() string {
	return "log dispatcher"
}

func (d *LogDispatcher) Shutdown() {
	atomic.StoreInt32(&d.shuttingDown, 1)
	d.msgCh <- nil
	<-d.doneProcessing
	logsInQueue.Set(0)
	for _, appD := range d.dispatchers {
		close(appD.done)
		<-appD.finished
	}
}

type appLogDispatcher struct {
	appName  string
	done     chan struct{}
	finished chan struct{}
	toFlush  chan *Applog
}

func newAppLogDispatcher(appName string) *appLogDispatcher {
	d := &appLogDispatcher{
		appName:  appName,
		done:     make(chan struct{}),
		finished: make(chan struct{}),
		toFlush:  make(chan *Applog),
	}
	go d.runFlusher()
	return d
}

func (d *appLogDispatcher) runFlusher() {
	defer close(d.finished)
	t := time.NewTimer(bulkMaxWaitTime)
	pos := 0
	bulkBuffer := make([]interface{}, bulkMaxNumberMsgs)
	shouldReturn := false
	for {
		var flush bool
		select {
		case <-d.done:
			flush = pos > 0
			shouldReturn = true
		case msg := <-d.toFlush:
			if pos == bulkMaxNumberMsgs {
				flush = true
				break
			}
			bulkBuffer[pos] = msg
			pos++
			flush = bulkMaxNumberMsgs == pos
		case <-t.C:
			flush = pos > 0
			t.Reset(bulkMaxWaitTime)
		}
		if flush {
			conn, err := db.LogConn()
			if err != nil {
				log.Errorf("[log flusher] unable to connect to mongodb: %s", err)
				continue
			}
			coll := conn.Logs(d.appName)
			err = coll.Insert(bulkBuffer[:pos]...)
			coll.Close()
			if err != nil {
				log.Errorf("[log flusher] unable to insert logs: %s", err)
				continue
			}
			logsWritten.Add(float64(pos))
			pos = 0
		}
		if shouldReturn {
			return
		}
	}
}
<|endoftext|>