<commit_before>package ble

import (
	"context"
	"log"
	"strings"
	"sync"

	"gobot.io/x/gobot"

	blelib "github.com/currantlabs/ble"
	"github.com/pkg/errors"
)

var currentDevice *blelib.Device
var bleMutex sync.Mutex
var bleCtx context.Context

type BLEConnector interface {
	Connect() error
	Reconnect() error
	Disconnect() error
	Finalize() error
	Name() string
	SetName(string)
	Address() string
	ReadCharacteristic(string) ([]byte, error)
	WriteCharacteristic(string, []byte) error
	Subscribe(string, func([]byte, error)) error
}

// ClientAdaptor represents a Client Connection to a BLE Peripheral
type ClientAdaptor struct {
	name    string
	address string

	addr    blelib.Addr
	device  *blelib.Device
	client  blelib.Client
	profile *blelib.Profile

	connected bool
	ready     chan struct{}
}

// NewClientAdaptor returns a new ClientAdaptor given an address or peripheral name
func NewClientAdaptor(address string) *ClientAdaptor {
	return &ClientAdaptor{
		name:      gobot.DefaultName("BLEClient"),
		address:   address,
		connected: false,
	}
}

// Name returns the name for the adaptor
func (b *ClientAdaptor) Name() string { return b.name }

// SetName sets the name for the adaptor
func (b *ClientAdaptor) SetName(n string) { b.name = n }

// Address returns the Bluetooth LE address for the adaptor
func (b *ClientAdaptor) Address() string { return b.address }

// Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
func (b *ClientAdaptor) Connect() (err error) {
	bleMutex.Lock()
	defer bleMutex.Unlock()

	b.device, err = getBLEDevice("default")
	if err != nil {
		return errors.Wrap(err, "can't connect")
	}

	var cln blelib.Client

	cln, err = blelib.Connect(context.Background(), filter(b.Address()))
	if err != nil {
		return errors.Wrap(err, "can't connect")
	}

	b.addr = cln.Address()
	b.address = cln.Address().String()
	b.SetName(cln.Name())
	b.client = cln

	p, err := b.client.DiscoverProfile(true)
	if err != nil {
		return errors.Wrap(err, "can't discover profile")
	}

	b.profile = p
	b.connected = true
	return
}

// Reconnect attempts to reconnect to the BLE peripheral. If it has an active connection
// it will first close that connection and then establish a new connection.
// Returns true on Successful reconnection
func (b *ClientAdaptor) Reconnect() (err error) {
	if b.connected {
		b.Disconnect()
	}
	return b.Connect()
}

// Disconnect terminates the connection to the BLE peripheral. Returns true on successful disconnect.
func (b *ClientAdaptor) Disconnect() (err error) {
	b.client.CancelConnection()
	return
}

// Finalize finalizes the BLEAdaptor
func (b *ClientAdaptor) Finalize() (err error) {
	return b.Disconnect()
}

// ReadCharacteristic returns bytes from the BLE device for the
// requested characteristic uuid
func (b *ClientAdaptor) ReadCharacteristic(cUUID string) (data []byte, err error) {
	if !b.connected {
		log.Fatalf("Cannot read from BLE device until connected")
		return
	}

	// bleMutex.Lock()
	// defer bleMutex.Unlock()

	uuid, _ := blelib.Parse(cUUID)

	if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
		data, err = b.client.ReadCharacteristic(u.(*blelib.Characteristic))
	}

	return
}

// WriteCharacteristic writes bytes to the BLE device for the
// requested service and characteristic
func (b *ClientAdaptor) WriteCharacteristic(cUUID string, data []byte) (err error) {
	if !b.connected {
		log.Fatalf("Cannot write to BLE device until connected")
		return
	}

	// bleMutex.Lock()
	// defer bleMutex.Unlock()

	uuid, _ := blelib.Parse(cUUID)

	if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
		err = b.client.WriteCharacteristic(u.(*blelib.Characteristic), data, true)
	}

	return
}

// Subscribe subscribes to notifications from the BLE device for the
// requested service and characteristic
func (b *ClientAdaptor) Subscribe(cUUID string, f func([]byte, error)) (err error) {
	if !b.connected {
		log.Fatalf("Cannot subscribe to BLE device until connected")
		return
	}

	// bleMutex.Lock()
	// defer bleMutex.Unlock()

	uuid, _ := blelib.Parse(cUUID)

	if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
		h := func(req []byte) { f(req, nil) }
		err = b.client.Subscribe(u.(*blelib.Characteristic), false, h)
		if err != nil {
			return err
		}
		return nil
	}

	return
}

// getBLEDevice is singleton for blelib HCI device connection
func getBLEDevice(impl string) (d *blelib.Device, err error) {
	if currentDevice != nil {
		return currentDevice, nil
	}

	dev, e := defaultDevice(impl)
	if e != nil {
		return nil, errors.Wrap(e, "can't get device")
	}
	blelib.SetDefaultDevice(dev)

	currentDevice = &dev
	d = &dev
	return
}

func filter(name string) blelib.AdvFilter {
	return func(a blelib.Advertisement) bool {
		return strings.ToLower(a.LocalName()) == strings.ToLower(name) ||
			a.Address().String() == strings.ToLower(name)
	}
}
<commit_msg>ble: somehow this comment is needed for go1.8.1 to export this interface. Huh?<commit_after>package ble

import (
	"context"
	"log"
	"strings"
	"sync"

	"gobot.io/x/gobot"

	blelib "github.com/currantlabs/ble"
	"github.com/pkg/errors"
)

var currentDevice *blelib.Device
var bleMutex sync.Mutex
var bleCtx context.Context

// BLEConnector is the interface that a BLE ClientAdaptor must implement
type BLEConnector interface {
	Connect() error
	Reconnect() error
	Disconnect() error
	Finalize() error
	Name() string
	SetName(string)
	Address() string
	ReadCharacteristic(string) ([]byte, error)
	WriteCharacteristic(string, []byte) error
	Subscribe(string, func([]byte, error)) error
}

// ClientAdaptor represents a Client Connection to a BLE Peripheral
type ClientAdaptor struct {
	name    string
	address string

	addr    blelib.Addr
	device  *blelib.Device
	client  blelib.Client
	profile *blelib.Profile

	connected bool
	ready     chan struct{}
}

// NewClientAdaptor returns a new ClientAdaptor given an address or peripheral name
func NewClientAdaptor(address string) *ClientAdaptor {
	return &ClientAdaptor{
		name:      gobot.DefaultName("BLEClient"),
		address:   address,
		connected: false,
	}
}

// Name returns the name for the adaptor
func (b *ClientAdaptor) Name() string { return b.name }

// SetName sets the name for the adaptor
func (b *ClientAdaptor) SetName(n string) { b.name = n }

// Address returns the Bluetooth LE address for the adaptor
func (b *ClientAdaptor) Address() string { return b.address }

// Connect initiates a connection to the BLE peripheral. Returns true on successful connection.
func (b *ClientAdaptor) Connect() (err error) {
	bleMutex.Lock()
	defer bleMutex.Unlock()

	b.device, err = getBLEDevice("default")
	if err != nil {
		return errors.Wrap(err, "can't connect")
	}

	var cln blelib.Client

	cln, err = blelib.Connect(context.Background(), filter(b.Address()))
	if err != nil {
		return errors.Wrap(err, "can't connect")
	}

	b.addr = cln.Address()
	b.address = cln.Address().String()
	b.SetName(cln.Name())
	b.client = cln

	p, err := b.client.DiscoverProfile(true)
	if err != nil {
		return errors.Wrap(err, "can't discover profile")
	}

	b.profile = p
	b.connected = true
	return
}

// Reconnect attempts to reconnect to the BLE peripheral. If it has an active connection
// it will first close that connection and then establish a new connection.
// Returns true on Successful reconnection
func (b *ClientAdaptor) Reconnect() (err error) {
	if b.connected {
		b.Disconnect()
	}
	return b.Connect()
}

// Disconnect terminates the connection to the BLE peripheral. Returns true on successful disconnect.
func (b *ClientAdaptor) Disconnect() (err error) {
	b.client.CancelConnection()
	return
}

// Finalize finalizes the BLEAdaptor
func (b *ClientAdaptor) Finalize() (err error) {
	return b.Disconnect()
}

// ReadCharacteristic returns bytes from the BLE device for the
// requested characteristic uuid
func (b *ClientAdaptor) ReadCharacteristic(cUUID string) (data []byte, err error) {
	if !b.connected {
		log.Fatalf("Cannot read from BLE device until connected")
		return
	}

	// bleMutex.Lock()
	// defer bleMutex.Unlock()

	uuid, _ := blelib.Parse(cUUID)

	if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
		data, err = b.client.ReadCharacteristic(u.(*blelib.Characteristic))
	}

	return
}

// WriteCharacteristic writes bytes to the BLE device for the
// requested service and characteristic
func (b *ClientAdaptor) WriteCharacteristic(cUUID string, data []byte) (err error) {
	if !b.connected {
		log.Fatalf("Cannot write to BLE device until connected")
		return
	}

	// bleMutex.Lock()
	// defer bleMutex.Unlock()

	uuid, _ := blelib.Parse(cUUID)

	if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
		err = b.client.WriteCharacteristic(u.(*blelib.Characteristic), data, true)
	}

	return
}

// Subscribe subscribes to notifications from the BLE device for the
// requested service and characteristic
func (b *ClientAdaptor) Subscribe(cUUID string, f func([]byte, error)) (err error) {
	if !b.connected {
		log.Fatalf("Cannot subscribe to BLE device until connected")
		return
	}

	// bleMutex.Lock()
	// defer bleMutex.Unlock()

	uuid, _ := blelib.Parse(cUUID)

	if u := b.profile.Find(blelib.NewCharacteristic(uuid)); u != nil {
		h := func(req []byte) { f(req, nil) }
		err = b.client.Subscribe(u.(*blelib.Characteristic), false, h)
		if err != nil {
			return err
		}
		return nil
	}

	return
}

// getBLEDevice is singleton for blelib HCI device connection
func getBLEDevice(impl string) (d *blelib.Device, err error) {
	if currentDevice != nil {
		return currentDevice, nil
	}

	dev, e := defaultDevice(impl)
	if e != nil {
		return nil, errors.Wrap(e, "can't get device")
	}
	blelib.SetDefaultDevice(dev)

	currentDevice = &dev
	d = &dev
	return
}

func filter(name string) blelib.AdvFilter {
	return func(a blelib.Advertisement) bool {
		return strings.ToLower(a.LocalName()) == strings.ToLower(name) ||
			a.Address().String() == strings.ToLower(name)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Workiva, LLC

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.
*/

/*
The priority queue is almost a spitting image of the logic
used for a regular queue.  In order to keep the logic fast,
this code is repeated instead of using casts to cast to interface{}
back and forth.  If Go had inheritance and generics, this problem
would be easier to solve.
*/

package queue

import "sync"

// Item is an item that can be added to the priority queue.
type Item interface {
	// Compare returns a bool that can be used to determine
	// ordering in the priority queue.  Assuming the queue
	// is in ascending order, this should return > logic.
	// Return 1 to indicate this object is greater than the
	// the other logic, 0 to indicate equality, and -1 to indicate
	// less than other.
	Compare(other Item) int
}

type priorityItems []Item

func (items *priorityItems) swap(i, j int) {
	(*items)[i], (*items)[j] = (*items)[j], (*items)[i]
}

func (items *priorityItems) pop() Item {
	size := len(*items)

	// Move last leaf to root, and 'pop' the last item.
	items.swap(size-1, 0)
	item := (*items)[size-1] // Item to return.
	*items = (*items)[:size-1]

	// 'Bubble down' to restore heap property.
	index := 0
	childL, childR := 2*index+1, 2*index+2
	for len(*items) > childL {
		child := childL
		if len(*items) > childR && (*items)[childR].Compare((*items)[childL]) < 0 {
			child = childR
		}

		if (*items)[child].Compare((*items)[index]) < 0 {
			items.swap(index, child)

			index = child
			childL, childR = 2*index+1, 2*index+2
		} else {
			break
		}
	}

	return item
}

func (items *priorityItems) get(number int) []Item {
	returnItems := make([]Item, 0, number)
	for i := 0; i < number; i++ {
		if i >= len(*items) {
			break
		}

		returnItems = append(returnItems, items.pop())
	}

	return returnItems
}

func (items *priorityItems) push(item Item) {
	// Stick the item as the end of the last level.
	*items = append(*items, item)

	// 'Bubble up' to restore heap property.
	index := len(*items) - 1
	parent := int((index - 1) / 2)
	for parent >= 0 && (*items)[parent].Compare(item) > 0 {
		items.swap(index, parent)

		index = parent
		parent = int((index - 1) / 2)
	}
}

// PriorityQueue is similar to queue except that it takes
// items that implement the Item interface and adds them
// to the queue in priority order.
type PriorityQueue struct {
	waiters     waiters
	items       priorityItems
	itemMap     map[Item]bool
	lock        sync.Mutex
	disposeLock sync.Mutex
	disposed    bool
}

// Put adds items to the queue.
func (pq *PriorityQueue) Put(items ...Item) error {
	if len(items) == 0 {
		return nil
	}

	pq.lock.Lock()
	if pq.disposed {
		pq.lock.Unlock()
		return ErrDisposed
	}

	for _, item := range items {
		if ok := pq.itemMap[item]; !ok {
			pq.itemMap[item] = true
			pq.items.push(item)
		}
	}

	for {
		sema := pq.waiters.get()
		if sema == nil {
			break
		}

		sema.response.Add(1)
		sema.ready <- true
		sema.response.Wait()
		if len(pq.items) == 0 {
			break
		}
	}

	pq.lock.Unlock()
	return nil
}

// Get retrieves items from the queue.  If the queue is empty,
// this call blocks until the next item is added to the queue.  This
// will attempt to retrieve number of items.
func (pq *PriorityQueue) Get(number int) ([]Item, error) {
	if number < 1 {
		return nil, nil
	}

	pq.lock.Lock()

	if pq.disposed {
		pq.lock.Unlock()
		return nil, ErrDisposed
	}

	var items []Item

	// Remove references to popped items.
	deleteItems := func(items []Item) {
		for _, item := range items {
			pq.itemMap[item] = false
		}
	}

	if len(pq.items) == 0 {
		sema := newSema()
		pq.waiters.put(sema)
		pq.lock.Unlock()

		<-sema.ready
		pq.disposeLock.Lock()
		if pq.disposed {
			pq.disposeLock.Unlock()
			return nil, ErrDisposed
		}
		pq.disposeLock.Unlock()

		items = pq.items.get(number)
		deleteItems(items)
		sema.response.Done()
		return items, nil
	}

	items = pq.items.get(number)
	deleteItems(items)
	pq.lock.Unlock()
	return items, nil
}

// Peek will look at the next item without removing it from the queue.
func (pq *PriorityQueue) Peek() Item {
	pq.lock.Lock()
	defer pq.lock.Unlock()
	if len(pq.items) > 0 {
		return pq.items[0]
	}
	return nil
}

// Empty returns a bool indicating if there are any items left
// in the queue.
func (pq *PriorityQueue) Empty() bool {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	return len(pq.items) == 0
}

// Len returns a number indicating how many items are in the queue.
func (pq *PriorityQueue) Len() int {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	return len(pq.items)
}

// Disposed returns a bool indicating if this queue has been disposed.
func (pq *PriorityQueue) Disposed() bool {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	return pq.disposed
}

// Dispose will prevent any further reads/writes to this queue
// and frees available resources.
func (pq *PriorityQueue) Dispose() {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	pq.disposeLock.Lock()
	defer pq.disposeLock.Unlock()

	pq.disposed = true
	for _, waiter := range pq.waiters {
		waiter.response.Add(1)
		waiter.ready <- true
	}

	pq.items = nil
	pq.waiters = nil
}

// NewPriorityQueue is the constructor for a priority queue.
func NewPriorityQueue(hint int) *PriorityQueue {
	return &PriorityQueue{
		items:   make(priorityItems, 0, hint),
		itemMap: make(map[Item]bool, hint),
	}
}
<commit_msg>Use empty struct in set values<commit_after>/*
Copyright 2014 Workiva, LLC

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.
*/

/*
The priority queue is almost a spitting image of the logic
used for a regular queue.  In order to keep the logic fast,
this code is repeated instead of using casts to cast to interface{}
back and forth.  If Go had inheritance and generics, this problem
would be easier to solve.
*/

package queue

import "sync"

// Item is an item that can be added to the priority queue.
type Item interface {
	// Compare returns a bool that can be used to determine
	// ordering in the priority queue.  Assuming the queue
	// is in ascending order, this should return > logic.
	// Return 1 to indicate this object is greater than the
	// the other logic, 0 to indicate equality, and -1 to indicate
	// less than other.
	Compare(other Item) int
}

type priorityItems []Item

func (items *priorityItems) swap(i, j int) {
	(*items)[i], (*items)[j] = (*items)[j], (*items)[i]
}

func (items *priorityItems) pop() Item {
	size := len(*items)

	// Move last leaf to root, and 'pop' the last item.
	items.swap(size-1, 0)
	item := (*items)[size-1] // Item to return.
	*items = (*items)[:size-1]

	// 'Bubble down' to restore heap property.
	index := 0
	childL, childR := 2*index+1, 2*index+2
	for len(*items) > childL {
		child := childL
		if len(*items) > childR && (*items)[childR].Compare((*items)[childL]) < 0 {
			child = childR
		}

		if (*items)[child].Compare((*items)[index]) < 0 {
			items.swap(index, child)

			index = child
			childL, childR = 2*index+1, 2*index+2
		} else {
			break
		}
	}

	return item
}

func (items *priorityItems) get(number int) []Item {
	returnItems := make([]Item, 0, number)
	for i := 0; i < number; i++ {
		if i >= len(*items) {
			break
		}

		returnItems = append(returnItems, items.pop())
	}

	return returnItems
}

func (items *priorityItems) push(item Item) {
	// Stick the item as the end of the last level.
	*items = append(*items, item)

	// 'Bubble up' to restore heap property.
	index := len(*items) - 1
	parent := int((index - 1) / 2)
	for parent >= 0 && (*items)[parent].Compare(item) > 0 {
		items.swap(index, parent)

		index = parent
		parent = int((index - 1) / 2)
	}
}

// PriorityQueue is similar to queue except that it takes
// items that implement the Item interface and adds them
// to the queue in priority order.
type PriorityQueue struct {
	waiters     waiters
	items       priorityItems
	itemMap     map[Item]struct{}
	lock        sync.Mutex
	disposeLock sync.Mutex
	disposed    bool
}

// Put adds items to the queue.
func (pq *PriorityQueue) Put(items ...Item) error {
	if len(items) == 0 {
		return nil
	}

	pq.lock.Lock()
	if pq.disposed {
		pq.lock.Unlock()
		return ErrDisposed
	}

	for _, item := range items {
		if _, ok := pq.itemMap[item]; !ok {
			pq.itemMap[item] = struct{}{}
			pq.items.push(item)
		}
	}

	for {
		sema := pq.waiters.get()
		if sema == nil {
			break
		}

		sema.response.Add(1)
		sema.ready <- true
		sema.response.Wait()
		if len(pq.items) == 0 {
			break
		}
	}

	pq.lock.Unlock()
	return nil
}

// Get retrieves items from the queue.  If the queue is empty,
// this call blocks until the next item is added to the queue.  This
// will attempt to retrieve number of items.
func (pq *PriorityQueue) Get(number int) ([]Item, error) {
	if number < 1 {
		return nil, nil
	}

	pq.lock.Lock()

	if pq.disposed {
		pq.lock.Unlock()
		return nil, ErrDisposed
	}

	var items []Item

	// Remove references to popped items.
	deleteItems := func(items []Item) {
		for _, item := range items {
			delete(pq.itemMap, item)
		}
	}

	if len(pq.items) == 0 {
		sema := newSema()
		pq.waiters.put(sema)
		pq.lock.Unlock()

		<-sema.ready
		pq.disposeLock.Lock()
		if pq.disposed {
			pq.disposeLock.Unlock()
			return nil, ErrDisposed
		}
		pq.disposeLock.Unlock()

		items = pq.items.get(number)
		deleteItems(items)
		sema.response.Done()
		return items, nil
	}

	items = pq.items.get(number)
	deleteItems(items)
	pq.lock.Unlock()
	return items, nil
}

// Peek will look at the next item without removing it from the queue.
func (pq *PriorityQueue) Peek() Item {
	pq.lock.Lock()
	defer pq.lock.Unlock()
	if len(pq.items) > 0 {
		return pq.items[0]
	}
	return nil
}

// Empty returns a bool indicating if there are any items left
// in the queue.
func (pq *PriorityQueue) Empty() bool {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	return len(pq.items) == 0
}

// Len returns a number indicating how many items are in the queue.
func (pq *PriorityQueue) Len() int {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	return len(pq.items)
}

// Disposed returns a bool indicating if this queue has been disposed.
func (pq *PriorityQueue) Disposed() bool {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	return pq.disposed
}

// Dispose will prevent any further reads/writes to this queue
// and frees available resources.
func (pq *PriorityQueue) Dispose() {
	pq.lock.Lock()
	defer pq.lock.Unlock()

	pq.disposeLock.Lock()
	defer pq.disposeLock.Unlock()

	pq.disposed = true
	for _, waiter := range pq.waiters {
		waiter.response.Add(1)
		waiter.ready <- true
	}

	pq.items = nil
	pq.waiters = nil
}

// NewPriorityQueue is the constructor for a priority queue.
func NewPriorityQueue(hint int) *PriorityQueue {
	return &PriorityQueue{
		items:   make(priorityItems, 0, hint),
		itemMap: make(map[Item]struct{}, hint),
	}
}
<|endoftext|>


<commit_before>package xprocessor

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"sync"

	"github.com/mitchellh/mapstructure"
	"github.com/vjeantet/bitfan/api/models"
	"github.com/vjeantet/bitfan/codecs"
	"github.com/vjeantet/bitfan/processors"
)

func NewWithSpec(spec *models.XProcessor) processors.Processor {
	opt := &options{
		Behavior:              spec.Behavior,
		Stream:                spec.Stream,
		Command:               spec.Command,
		Args:                  spec.Args,
		Kind:                  spec.Kind,
		Code:                  spec.Code,
		StdinAs:               spec.StdinAs,
		StdoutAs:              spec.StdoutAs,
		OptionsCompositionTpl: spec.OptionsCompositionTpl,
	}

	if spec.Stream == true {
		p := &streamProcessor{}
		p.opt = opt
		return p
	} else {
		p := &noStreamProcessor{}
		p.opt = opt
		return p
	}
}

const (
	TRANSFORMER string = "transformer"
	CONSUMER    string = "consumer"
	PRODUCER    string = "producer"
)

type options struct {
	processors.CommonOptions `mapstructure:",squash"`

	Codec codecs.CodecCollection

	// Producer ? Consumer ? Transformer ?
	// @Enum "producer","transformer","consumer"
	// @Default "transformer"
	Behavior string `mapstructure:"behavior" validate:"required"`

	// Delegated processor is started one time and receives events through its stdin.
	// When it should be started for each received event set value to "false"
	// @Default false
	Stream bool `mapstructure:"stream" `

	// Path to the bin used as delegated processor
	Command string   `mapstructure:"command" validate:"required"`
	Args    []string `mapstructure:"args" `
	Code    string   `mapstructure:"code"`

	Kind string `mapstructure:"kind"`

	// What is the value's format of stdinputed value
	// @Default "json","none"
	StdinAs string `mapstructure:"stdin_as" validate:"required"`

	// What is the value's format of stdoutputed value
	// @Enum "json","string"
	// @Default "json"
	StdoutAs string `mapstructure:"stdout_as" validate:"required"`

	OptionsCompositionTpl string `mapstructure:"options_composition_tpl"`

	// Flags for delegated processors (will be passed as args)
	// @default {"Content-Type" => "application/json"}
	Flags map[string]interface{}
}

// Reads events from standard input
type processor struct {
	processors.Base

	opt *options

	flagsTpl *template.Template

	wg *sync.WaitGroup
	q  chan bool
}

func (p *processor) Configure(ctx processors.ProcessorContext, conf map[string]interface{}) error {
	// remove common option from conf
	if err := mapstructure.WeakDecode(conf, &p.opt.CommonOptions); err != nil {
		return err
	}
	delete(conf, "add_field")
	delete(conf, "type")
	delete(conf, "remove_tag")
	delete(conf, "remove_field")
	delete(conf, "add_tag")
	delete(conf, "trace")
	delete(conf, "interval") // todo remove only when producer noStream
	delete(conf, "workers")

	// Set processor's user options
	if err := mapstructure.WeakDecode(conf, &p.opt.Flags); err != nil {
		return err
	}

	p.opt.Codec = codecs.CodecCollection{}
	if p.opt.StdinAs == "json" {
		p.opt.Codec.Enc = codecs.New("json", nil, ctx.Log(), ctx.ConfigWorkingLocation())
	}

	if p.opt.StdinAs == "line" {
		p.opt.Codec.Enc = codecs.New("line", map[string]interface{}{"format": "{{.message}}"}, ctx.Log(), ctx.ConfigWorkingLocation())
	}

	if p.opt.StdoutAs == "json" {
		p.opt.Codec.Dec = codecs.New("json", nil, ctx.Log(), ctx.ConfigWorkingLocation())
	}
	if p.opt.StdoutAs == "line" {
		p.opt.Codec.Dec = codecs.New("line", nil, ctx.Log(), ctx.ConfigWorkingLocation())
	}

	if p.opt.Behavior != TRANSFORMER && p.opt.Behavior != CONSUMER && p.opt.Behavior != PRODUCER {
		return fmt.Errorf("unknow behavior '%s'", p.opt.Behavior)
	}

	err := p.ConfigureAndValidate(ctx, conf, p.opt)
	if err != nil {
		return err
	}

	if p.opt.OptionsCompositionTpl != "" {
		//Prepare templates

		funcMap := template.FuncMap{
			"isSlice": func(a interface{}) (bool, error) {
				av := reflect.ValueOf(a).Kind()
				return av == reflect.Slice, nil
			},
		}
		// p.opt.OptionsCompositionTpl = `{{ range $name, $value := .Options }}{{if ne $name "_" }}{{if isSlice $value}}{{ range $value }}--{{$name}}="{{.}}" {{end}}{{else}}--{{$name}}="{{$value}}" {{end}}{{ end }}{{ end }}{{ index .Options "_"}}`

		tpl, err := template.New("").Option("missingkey=zero").Funcs(funcMap).Parse(p.opt.OptionsCompositionTpl)
		if err != nil {
			return err
		}
		p.flagsTpl = tpl
	}

	switch p.opt.Kind {
	case "php":
		if strings.HasPrefix(p.opt.Code, "<?php") {
			p.opt.Code = p.opt.Code[5:]
		}
		p.opt.Command = "php"
		p.opt.Args = []string{"-d", "display_errors=stderr", "-r", p.opt.Code, "--"}
	case "python":
		p.opt.Command = "python"
		p.opt.Args = []string{"-u", "-c", p.opt.Code}
	case "golang":
		tmpGoFile := filepath.Join(p.DataLocation, "main.go")

		err := ioutil.WriteFile(tmpGoFile, []byte(p.opt.Code), 0644)
		if err != nil {
			return err
		}

		p.opt.Command = "go"
		p.opt.Args = []string{"run", tmpGoFile}
	}

	return err
}

func (p *processor) buildCommandArgs(e processors.IPacket) []string {
	finalArgs := []string{}
	for _, v := range p.opt.Args {
		v := strings.TrimSpace(v)
		if v == "" {
			continue
		}
		finalArgs = append(finalArgs, v)
	}
	if p.flagsTpl == nil {
		for k, v := range p.opt.Flags {
			if k == "_" {
				continue
			}
			switch vt := v.(type) {
			case string:
				if v == "" {
					finalArgs = append(finalArgs, "--"+k)
				} else {
					if e != nil {
						processors.Dynamic(&vt, e.Fields())
					}
					finalArgs = append(finalArgs, "--"+k+"="+vt)
				}
			case int64:
				finalArgs = append(finalArgs, fmt.Sprintf("--%s=%d", k, vt))
			case bool:
				if vt == true {
					finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s", k, "true"))
				} else {
					finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s", k, "false"))
				}
			case []interface{}:
				for _, l := range vt {
					switch lt := l.(type) {
					case string:
						if e != nil {
							processors.Dynamic(&lt, e.Fields())
						}
						finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s", k, lt))
					case int64:
						finalArgs = append(finalArgs, fmt.Sprintf("--%s=%d", k, lt))
					default:
						p.Logger.Errorf("not handled slice type : %s=>%v", k, v)
					}
				}
			default:
				p.Logger.Errorf("not handled type : %s=>%v", k, v)
			}

		}
		if v, ok := p.opt.Flags["_"]; ok {
			switch vt := v.(type) {
			case string:
				finalArgs = append(finalArgs, vt)
			default:
				p.Logger.Errorf("not handled type : _ =>%v", v)
			}

		}
	} else { // use template
		buff := bytes.NewBufferString("")
		err := p.flagsTpl.Execute(buff, struct{ Options map[string]interface{} }{p.opt.Flags})
		if err != nil {
			p.Logger.Errorf("template error : %v", err)
			return finalArgs
		}
		finalArgs = append(finalArgs, strings.TrimSpace(buff.String()))
	}

	return finalArgs
}

func (p *processor) startCommand(e processors.IPacket) (*exec.Cmd, io.WriteCloser, io.ReadCloser, io.ReadCloser, error) {
	args := p.buildCommandArgs(e)

	var cmd *exec.Cmd

	cmd = exec.Command(p.opt.Command, args...)
	cmd.Dir = p.ConfigWorkingLocation
	cmd.Env = append(os.Environ(),
		fmt.Sprintf("BF_PIPELINE_UUID=%s", p.PipelineUUID),
		fmt.Sprintf("BF_PIPELINE_WORKING_PATH=%s", p.ConfigWorkingLocation),
		fmt.Sprintf("BF_PROCESSOR_DATA_PATH=%s", p.DataLocation),
		fmt.Sprintf("BF_PROCESSOR_NAME=%s", p.B().Name),
		fmt.Sprintf("BF_PROCESSOR_LABEL=%s", p.B().Label),
	)

	stdin, _ := cmd.StdinPipe()
	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()

	if err := cmd.Start(); err != nil {
		p.Logger.Errorf("processor start error [%s] with command='%s' %s", err.Error(), p.opt.Command, args)
		return cmd, stdin, stdout, stderr, err
	}
	p.Logger.Infof("processor started process PID=%d with command= '%s' %s", cmd.Process.Pid, p.opt.Command, args)
	return cmd, stdin, stdout, stderr, nil
}

func (p *processor) readAndSendEventsFromProcess(dec codecs.Decoder) error {
	defer p.wg.Done()
	for {
		var record interface{}
		if err := dec.Decode(&record); err != nil {
			if err == io.EOF {
				p.Logger.Debugf("codec end of file : %s", err.Error())
				break
			} else {
				p.Logger.Errorln("codec error : ", err.Error())
				return err
			}
		}

		var ne processors.IPacket
		switch v := record.(type) {
		case string:
			ne = p.NewPacket(map[string]interface{}{
				"message": v,
			})
		case map[string]interface{}:
			ne = p.NewPacket(v)
		case []interface{}:
			ne = p.NewPacket(map[string]interface{}{
				"data": v,
			})
		default:
			p.Logger.Errorf("Unknow structure %#v", v)
			continue
		}

		p.opt.ProcessCommonOptions(ne.Fields())
		p.Send(ne)
	}
	return nil
}
<commit_msg>processors : xprocessor - assert map value type in args<commit_after>package xprocessor

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"sync"

	"github.com/mitchellh/mapstructure"
	"github.com/vjeantet/bitfan/api/models"
	"github.com/vjeantet/bitfan/codecs"
	"github.com/vjeantet/bitfan/processors"
)

func NewWithSpec(spec *models.XProcessor) processors.Processor {
	opt := &options{
		Behavior:              spec.Behavior,
		Stream:                spec.Stream,
		Command:               spec.Command,
		Args:                  spec.Args,
		Kind:                  spec.Kind,
		Code:                  spec.Code,
		StdinAs:               spec.StdinAs,
		StdoutAs:              spec.StdoutAs,
		OptionsCompositionTpl: spec.OptionsCompositionTpl,
	}

	if spec.Stream == true {
		p := &streamProcessor{}
		p.opt = opt
		return p
	} else {
		p := &noStreamProcessor{}
		p.opt = opt
		return p
	}
}

const (
	TRANSFORMER string = "transformer"
	CONSUMER    string = "consumer"
	PRODUCER    string = "producer"
)

type options struct {
	processors.CommonOptions `mapstructure:",squash"`

	Codec codecs.CodecCollection

	// Producer ? Consumer ? Transformer ?
	// @Enum "producer","transformer","consumer"
	// @Default "transformer"
	Behavior string `mapstructure:"behavior" validate:"required"`

	// Delegated processor is started one time and receives events through its stdin.
	// When it should be started for each received event set value to "false"
	// @Default false
	Stream bool `mapstructure:"stream" `

	// Path to the bin used as delegated processor
	Command string   `mapstructure:"command" validate:"required"`
	Args    []string `mapstructure:"args" `
	Code    string   `mapstructure:"code"`

	Kind string `mapstructure:"kind"`

	// What is the value's format of stdinputed value
	// @Default "json","none"
	StdinAs string `mapstructure:"stdin_as" validate:"required"`

	// What is the value's format of stdoutputed value
	// @Enum "json","string"
	// @Default "json"
	StdoutAs string `mapstructure:"stdout_as" validate:"required"`

	OptionsCompositionTpl string `mapstructure:"options_composition_tpl"`

	// Flags for delegated processors (will be passed as args)
	// @default {"Content-Type" => "application/json"}
	Flags map[string]interface{}
}

// Reads events from standard input
type processor struct {
	processors.Base

	opt *options

	flagsTpl *template.Template

	wg *sync.WaitGroup
	q  chan bool
}

func (p *processor) Configure(ctx processors.ProcessorContext, conf map[string]interface{}) error {
	// remove common option from conf
	if err := mapstructure.WeakDecode(conf, &p.opt.CommonOptions); err != nil {
		return err
	}
	delete(conf, "add_field")
	delete(conf, "type")
	delete(conf, "remove_tag")
	delete(conf, "remove_field")
	delete(conf, "add_tag")
	delete(conf, "trace")
	delete(conf, "interval") // todo remove only when producer noStream
	delete(conf, "workers")

	// Set processor's user options
	if err := mapstructure.WeakDecode(conf, &p.opt.Flags); err != nil {
		return err
	}

	p.opt.Codec = codecs.CodecCollection{}
	if p.opt.StdinAs == "json" {
		p.opt.Codec.Enc = codecs.New("json", nil, ctx.Log(), ctx.ConfigWorkingLocation())
	}

	if p.opt.StdinAs == "line" {
		p.opt.Codec.Enc = codecs.New("line", map[string]interface{}{"format": "{{.message}}"}, ctx.Log(), ctx.ConfigWorkingLocation())
	}

	if p.opt.StdoutAs == "json" {
		p.opt.Codec.Dec = codecs.New("json", nil, ctx.Log(), ctx.ConfigWorkingLocation())
	}
	if p.opt.StdoutAs == "line" {
		p.opt.Codec.Dec = codecs.New("line", nil, ctx.Log(), ctx.ConfigWorkingLocation())
	}

	if p.opt.Behavior != TRANSFORMER && p.opt.Behavior != CONSUMER && p.opt.Behavior != PRODUCER {
		return fmt.Errorf("unknow behavior '%s'", p.opt.Behavior)
	}

	err := p.ConfigureAndValidate(ctx, conf, p.opt)
	if err != nil {
		return err
	}

	if p.opt.OptionsCompositionTpl != "" {
		//Prepare templates

		funcMap := template.FuncMap{
			"isSlice": func(a interface{}) (bool, error) {
				av := reflect.ValueOf(a).Kind()
				return av == reflect.Slice, nil
			},
		}
		// p.opt.OptionsCompositionTpl = `{{ range $name, $value := .Options }}{{if ne $name "_" }}{{if isSlice $value}}{{ range $value }}--{{$name}}="{{.}}" {{end}}{{else}}--{{$name}}="{{$value}}" {{end}}{{ end }}{{ end }}{{ index .Options "_"}}`

		tpl, err := template.New("").Option("missingkey=zero").Funcs(funcMap).Parse(p.opt.OptionsCompositionTpl)
		if err != nil {
			return err
		}
		p.flagsTpl = tpl
	}

	switch p.opt.Kind {
	case "php":
		if strings.HasPrefix(p.opt.Code, "<?php") {
			p.opt.Code = p.opt.Code[5:]
		}
		p.opt.Command = "php"
		p.opt.Args = []string{"-d", "display_errors=stderr", "-r", p.opt.Code, "--"}
	case "python":
		p.opt.Command = "python"
		p.opt.Args = []string{"-u", "-c", p.opt.Code}
	case "golang":
		tmpGoFile := filepath.Join(p.DataLocation, "main.go")

		err := ioutil.WriteFile(tmpGoFile, []byte(p.opt.Code), 0644)
		if err != nil {
			return err
		}

		p.opt.Command = "go"
		p.opt.Args = []string{"run", tmpGoFile}
	}

	return err
}

func (p *processor) buildCommandArgs(e processors.IPacket) []string {
	finalArgs := []string{}
	for _, v := range p.opt.Args {
		v := strings.TrimSpace(v)
		if v == "" {
			continue
		}
		finalArgs = append(finalArgs, v)
	}
	if p.flagsTpl == nil {
		for k, v := range p.opt.Flags {
			if k == "_" {
				continue
			}
			switch vt := v.(type) {
			case string:
				if v == "" {
					finalArgs = append(finalArgs, "--"+k)
				} else {
					if e != nil {
						processors.Dynamic(&vt, e.Fields())
					}
					finalArgs = append(finalArgs, "--"+k+"="+vt)
				}
			case int64:
				finalArgs = append(finalArgs, fmt.Sprintf("--%s=%d", k, vt))
			case bool:
				if vt == true {
					finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s", k, "true"))
				} else {
					finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s", k, "false"))
				}
			case []interface{}:
				for _, l := range vt {
					switch lt := l.(type) {
					case string:
						if e != nil {
							processors.Dynamic(&lt, e.Fields())
						}
						finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s", k, lt))
					case int64:
						finalArgs = append(finalArgs, fmt.Sprintf("--%s=%d", k, lt))
					default:
						p.Logger.Errorf("not handled slice type : %s=>%v", k, v)
					}
				}
			case map[string]interface{}:
				for key, value := range vt {
					switch valuet := value.(type) {
					case string:
						finalArgs = append(finalArgs, fmt.Sprintf("--%s=%s:%s", k, key, valuet))
					default:
						p.Logger.Errorf("not handled map value type : %s=>%s", key, value)
					}
				}

			default:
				p.Logger.Errorf("not handled type : %s=>%v", k, v)
			}

		}
		if v, ok := p.opt.Flags["_"]; ok {
			switch vt := v.(type) {
			case string:
				finalArgs = append(finalArgs, vt)
			default:
				p.Logger.Errorf("not handled type : _ =>%v", v)
			}

		}
	} else { // use template
		buff := bytes.NewBufferString("")
		err := p.flagsTpl.Execute(buff, struct{ Options map[string]interface{} }{p.opt.Flags})
		if err != nil {
			p.Logger.Errorf("template error : %v", err)
			return finalArgs
		}
		finalArgs = append(finalArgs, strings.TrimSpace(buff.String()))
	}

	return finalArgs
}

func (p *processor) startCommand(e processors.IPacket) (*exec.Cmd, io.WriteCloser, io.ReadCloser, io.ReadCloser, error) {
	args := p.buildCommandArgs(e)

	var cmd *exec.Cmd

	cmd = exec.Command(p.opt.Command, args...)
	cmd.Dir = p.ConfigWorkingLocation
	cmd.Env = append(os.Environ(),
		fmt.Sprintf("BF_PIPELINE_UUID=%s", p.PipelineUUID),
		fmt.Sprintf("BF_PIPELINE_WORKING_PATH=%s", p.ConfigWorkingLocation),
		fmt.Sprintf("BF_PROCESSOR_DATA_PATH=%s", p.DataLocation),
		fmt.Sprintf("BF_PROCESSOR_NAME=%s", p.B().Name),
		fmt.Sprintf("BF_PROCESSOR_LABEL=%s", p.B().Label),
	)

	stdin, _ := cmd.StdinPipe()
	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()

	if err := cmd.Start(); err != nil {
		p.Logger.Errorf("processor start error [%s] with command='%s' %s", err.Error(), p.opt.Command, args)
		return cmd, stdin, stdout, stderr, err
	}
	p.Logger.Infof("processor started process PID=%d with command= '%s' %s", cmd.Process.Pid, p.opt.Command, args)
	return cmd, stdin, stdout, stderr, nil
}

func (p *processor) readAndSendEventsFromProcess(dec codecs.Decoder) error {
	defer p.wg.Done()
	for {
		var record interface{}
		if err := dec.Decode(&record); err != nil {
			if err == io.EOF {
				p.Logger.Debugf("codec end of file : %s", err.Error())
				break
			} else {
				p.Logger.Errorln("codec error : ", err.Error())
				return err
			}
		}

		var ne processors.IPacket
		switch v := record.(type) {
		case string:
			ne = p.NewPacket(map[string]interface{}{
				"message": v,
			})
		case map[string]interface{}:
			ne = p.NewPacket(v)
		case []interface{}:
			ne = p.NewPacket(map[string]interface{}{
				"data": v,
			})
		default:
			p.Logger.Errorf("Unknow structure %#v", v)
			continue
		}

		p.opt.ProcessCommonOptions(ne.Fields())
		p.Send(ne)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015-2017 trivago GmbH
//
// 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 producer

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/trivago/gollum/core"
	"github.com/trivago/tgo/tnet"
	"net/http"
	"sync"
	"github.com/trivago/gollum/healthcheck"
	"io/ioutil"
	"errors"
)

// HTTPRequest producer plugin
// The HTTPRequest producers sends messages as HTTP packet to a given webserver.
// This producer uses a fuse breaker when a request fails with an error
// code > 400 or the connection is down.
// Configuration example
//
//  - "producer.HTTPRequest":
//    RawData: true
//    Encoding: "text/plain; charset=utf-8"
//    Address: "localhost:80"
//
// Address defines the webserver to send http requests to. Set to "localhost:80"
// by default.
//
// RawData switches between creating POST data from the incoming message (false)
// and passing the message as HTTP request without changes (true).
// This setting is enabled by default.
//
// Encoding defines the payload encoding when RawData is set to false.
// Set to "text/plain; charset=utf-8" by default.
type HTTPRequest struct {
	core.BufferedProducer
	host       string
	port       string
	protocol   string
	address    string
	encoding   string
	rawPackets bool
	listen     *tnet.StopListener
	lastError  error
}

func init() {
	core.TypeRegistry.Register(HTTPRequest{})
}

// Configure initializes this producer with values from a plugin config.
func (prod *HTTPRequest) Configure(conf core.PluginConfigReader) error {
	var err error
	prod.BufferedProducer.Configure(conf)
	prod.SetStopCallback(prod.close)
	prod.SetCheckFuseCallback(prod.isHostUp)

	address := conf.GetString("Address", "localhost:80")
	prod.protocol, prod.host, prod.port, err = tnet.SplitAddress(address, "http")
	conf.Errors.Push(err)

	if prod.host == "" {
		prod.host = "localhost"
	}

	prod.address = fmt.Sprintf("%s://%s:%s", prod.protocol, prod.host, prod.port)
	prod.encoding = conf.GetString("Encoding", "text/plain; charset=utf-8")
	prod.rawPackets = conf.GetBool("RawData", true)

	//prod.Log.Debug.Printf(
	//"protocol=%s, host=%s, port=%s, address=%s, encoding=%s, rawPackets=%d",
	//	prod.protocol, prod.host, prod.port, prod.address, prod.encoding, prod.rawPackets)

	// Health check to ping the backend with an HTTP GET
	healthcheck.AddEndpointPathArray(
		[]string{conf.GetTypename(), prod.GetID(), "pingBackend"},
		func()(int, string){
			return prod.healthcheckPingBackend()
		},
	)

	// Health check to check the last result
	// TBD: This may be meaningless in a high-traffic environment; a statistics
	// based check could make more sense.
	healthcheck.AddEndpointPathArray(
		[]string{conf.GetTypename(), prod.GetID(), "lastError"},
		func()(int, string){
			if prod.lastError == nil {
				return healthcheck.StatusOK, "OK"
			}
			return healthcheck.StatusServiceUnavailable, fmt.Sprintf("ERROR: %s", prod.lastError)
		},
	)

	return conf.Errors.OrNil()
}

func (prod *HTTPRequest) healthcheckPingBackend() (int, string) {
	code, body, err := httpRequestWrapper(http.Get(prod.address))
	if err != nil {
		return code, fmt.Sprintf("%s", err)
	}
	return code, body
}

// Wrapper around the (*http.Response, error) values returned by HTTP clients.
//
// Reads the response body and code, returns (code int, body string err error).
// If the query succeeded with HTTP 200, err == nil
// If the query failed in some way, err contains a description of the error,
// code and body are populated whenever possible.
func httpRequestWrapper(resp *http.Response, err error) (int, string, error) {
	if err != nil {
		// Fail
		return healthcheck.StatusServiceUnavailable, "", err
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// Fail
		return resp.StatusCode, "", err
	}

	respBodyString := fmt.Sprintf("%s", respBody)

	err = nil
	if resp.StatusCode != http.StatusOK {
		err = errors.New(fmt.Sprintf("%d %s", resp.StatusCode, respBodyString))
	}
	return resp.StatusCode, respBodyString, err
}

func (prod *HTTPRequest) isHostUp() bool {
	resp, err := http.Get(prod.address)
	return err != nil && resp != nil && resp.StatusCode < 400
}

func (prod *HTTPRequest) sendReq(msg *core.Message) {
	var (
		req *http.Request
		err error
	)

	originalMsg := msg.Clone()
	requestData := bytes.NewBuffer(msg.Data())

	if prod.rawPackets {
		// Pass raw request
		req, err = http.ReadRequest(bufio.NewReader(requestData))

		if req != nil {
			req.URL.Host = prod.address
			req.RequestURI = ""
			req.URL.Scheme = prod.protocol
		}
	} else {
		// Convert to POST request
		req, err = http.NewRequest("POST", prod.address, requestData)
		if req != nil {
			req.Header.Add("content-type", prod.encoding)
		}
	}

	if err != nil {
		prod.Log.Error.Print("Invalid request", err)
		prod.Drop(originalMsg)
		prod.lastError = err
		return // ### return, malformed request ###
	}

	go func() {
		_, _, err := httpRequestWrapper(http.DefaultClient.Do(req))
		prod.Log.Debug.Printf("lastError: %s", prod.lastError)

		prod.lastError = err

		// Fail
		if err != nil {
			prod.Log.Error.Print("Send failed: ", err)
			if !prod.isHostUp() {
				prod.Control() <- core.PluginControlFuseBurn
			}
			prod.Drop(originalMsg)
			return
		}
		// Success
		prod.Control() <- core.PluginControlFuseActive
	}()
}

func (prod *HTTPRequest) close() {
	defer prod.WorkerDone()
	prod.DefaultClose()
}

// Produce writes to stdout or stderr.
func (prod *HTTPRequest) Produce(workers *sync.WaitGroup) {
	prod.AddMainWorker(workers)
	prod.MessageControlLoop(prod.sendReq)
}
<commit_msg>Cleanup<commit_after>// Copyright 2015-2017 trivago GmbH
//
// 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 producer

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/trivago/gollum/core"
	"github.com/trivago/tgo/tnet"
	"net/http"
	"sync"
	"github.com/trivago/gollum/healthcheck"
	"io/ioutil"
	"errors"
)

// HTTPRequest producer plugin
// The HTTPRequest producers sends messages as HTTP packet to a given webserver.
// This producer uses a fuse breaker when a request fails with an error
// code > 400 or the connection is down.
// Configuration example
//
//  - "producer.HTTPRequest":
//    RawData: true
//    Encoding: "text/plain; charset=utf-8"
//    Address: "localhost:80"
//
// Address defines the webserver to send http requests to. Set to "localhost:80"
// by default.
//
// RawData switches between creating POST data from the incoming message (false)
// and passing the message as HTTP request without changes (true).
// This setting is enabled by default.
//
// Encoding defines the payload encoding when RawData is set to false.
// Set to "text/plain; charset=utf-8" by default.
type HTTPRequest struct {
	core.BufferedProducer
	host       string
	port       string
	protocol   string
	address    string
	encoding   string
	rawPackets bool
	listen     *tnet.StopListener
	lastError  error
}

func init() {
	core.TypeRegistry.Register(HTTPRequest{})
}

// Configure initializes this producer with values from a plugin config.
func (prod *HTTPRequest) Configure(conf core.PluginConfigReader) error {
	var err error
	prod.BufferedProducer.Configure(conf)
	prod.SetStopCallback(prod.close)
	prod.SetCheckFuseCallback(prod.isHostUp)

	address := conf.GetString("Address", "localhost:80")
	prod.protocol, prod.host, prod.port, err = tnet.SplitAddress(address, "http")
	conf.Errors.Push(err)

	if prod.host == "" {
		prod.host = "localhost"
	}

	prod.address = fmt.Sprintf("%s://%s:%s", prod.protocol, prod.host, prod.port)
	prod.encoding = conf.GetString("Encoding", "text/plain; charset=utf-8")
	prod.rawPackets = conf.GetBool("RawData", true)

	//prod.Log.Debug.Printf(
	//"protocol=%s, host=%s, port=%s, address=%s, encoding=%s, rawPackets=%d",
	//	prod.protocol, prod.host, prod.port, prod.address, prod.encoding, prod.rawPackets)

	// Health check to ping the backend with an HTTP GET
	healthcheck.AddEndpointPathArray(
		[]string{conf.GetTypename(), prod.GetID(), "pingBackend"},
		func()(int, string){
			return prod.healthcheckPingBackend()
		},
	)

	// Health check to check the last result
	// TBD: This may be meaningless in a high-traffic environment; a statistics
	// based check could make more sense.
	healthcheck.AddEndpointPathArray(
		[]string{conf.GetTypename(), prod.GetID(), "lastError"},
		func()(int, string){
			if prod.lastError == nil {
				return healthcheck.StatusOK, "OK"
			}
			return healthcheck.StatusServiceUnavailable, fmt.Sprintf("ERROR: %s", prod.lastError)
		},
	)

	return conf.Errors.OrNil()
}

func (prod *HTTPRequest) healthcheckPingBackend() (int, string) {
	code, body, err := httpRequestWrapper(http.Get(prod.address))
	if err != nil {
		return code, fmt.Sprintf("%s", err)
	}
	return code, body
}

// Wrapper around the (*http.Response, error) values returned by HTTP clients.
//
// Reads the response body and code, returns (code int, body string err error).
// If the query succeeded with HTTP 200, err == nil
// If the query failed in some way, err contains a description of the error,
// code and body are populated whenever possible.
func httpRequestWrapper(resp *http.Response, err error) (int, string, error) {
	if err != nil {
		// Fail
		return healthcheck.StatusServiceUnavailable, "", err
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// Fail
		return resp.StatusCode, "", err
	}

	respBodyString := fmt.Sprintf("%s", respBody)

	err = nil
	if resp.StatusCode != http.StatusOK {
		err = errors.New(fmt.Sprintf("%d %s", resp.StatusCode, respBodyString))
	}
	return resp.StatusCode, respBodyString, err
}

func (prod *HTTPRequest) isHostUp() bool {
	resp, err := http.Get(prod.address)
	return err != nil && resp != nil && resp.StatusCode < 400
}

func (prod *HTTPRequest) sendReq(msg *core.Message) {
	var (
		req *http.Request
		err error
	)

	originalMsg := msg.Clone()
	requestData := bytes.NewBuffer(msg.Data())

	if prod.rawPackets {
		// Pass raw request
		req, err = http.ReadRequest(bufio.NewReader(requestData))

		if req != nil {
			req.URL.Host = prod.address
			req.RequestURI = ""
			req.URL.Scheme = prod.protocol
		}
	} else {
		// Convert to POST request
		req, err = http.NewRequest("POST", prod.address, requestData)
		if req != nil {
			req.Header.Add("content-type", prod.encoding)
		}
	}

	if err != nil {
		prod.Log.Error.Print("Invalid request", err)
		prod.Drop(originalMsg)
		prod.lastError = err
		return // ### return, malformed request ###
	}

	go func() {
		_, _, err := httpRequestWrapper(http.DefaultClient.Do(req))
		prod.lastError = err
		if err != nil {
			// Fail
			prod.Log.Error.Print("Send failed: ", err)
			if !prod.isHostUp() {
				prod.Control() <- core.PluginControlFuseBurn
			}
			prod.Drop(originalMsg)
			return
		}
		// Success
		prod.Control() <- core.PluginControlFuseActive
	}()
}

func (prod *HTTPRequest) close() {
	defer prod.WorkerDone()
	prod.DefaultClose()
}

// Produce writes to stdout or stderr.
func (prod *HTTPRequest) Produce(workers *sync.WaitGroup) {
	prod.AddMainWorker(workers)
	prod.MessageControlLoop(prod.sendReq)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package mesh

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/golang/glog"
	"github.com/weaveworks/mesh"
	"k8s.io/kops/protokube/pkg/gossip"
)

type MeshGossiper struct {
	seeds gossip.SeedProvider

	router *mesh.Router
	peer   *peer

	version uint64

	lastSnapshot *gossip.GossipStateSnapshot
}

func NewMeshGossiper(listen string, channelName string, nodeName string, password []byte, seeds gossip.SeedProvider) (*MeshGossiper, error) {

	connLimit := 64
	gossipDnsConnLimit := os.Getenv("GOSSIP_DNS_CONN_LIMIT")
	if gossipDnsConnLimit != "" {
		limit, err := strconv.Atoi(gossipDnsConnLimit)
		if err != nil {
			return nil, fmt.Errorf("cannot parse env GOSSIP_DNS_CONN_LIMIT value: %v", gossipDnsConnLimit)
		}
		connLimit = limit
	}

	glog.Infof("gossip dns connection limit is:%d", connLimit)

	meshConfig := mesh.Config{
		ProtocolMinVersion: mesh.ProtocolMinVersion,
		Password:           password,
		ConnLimit:          connLimit,
		PeerDiscovery:      true,
		//TrustedSubnets:     []*net.IPNet{},
	}

	{
		host, portString, err := net.SplitHostPort(listen)
		if err != nil {
			return nil, fmt.Errorf("cannot parse -listen flag: %v", listen)
		}
		port, err := strconv.Atoi(portString)
		if err != nil {
			return nil, fmt.Errorf("cannot parse -listen flag: %v", listen)
		}
		meshConfig.Host = host
		meshConfig.Port = port
	}

	meshName, err := mesh.PeerNameFromUserInput(nodeName)
	if err != nil {
		return nil, fmt.Errorf("error parsing peer name: %v", err)
	}

	nickname := nodeName
	logger := &glogLogger{}
	router := mesh.NewRouter(meshConfig, meshName, nickname, mesh.NullOverlay{}, logger)

	peer := newPeer(meshName)
	gossip := router.NewGossip(channelName, peer)
	peer.register(gossip)

	gossiper := &MeshGossiper{
		seeds:  seeds,
		router: router,
		peer:   peer,
	}
	return gossiper, nil
}

func (g *MeshGossiper) Start() error {
	//glog.Infof("mesh router starting (%s)", *meshListen)
	g.router.Start()

	defer func() {
		glog.Infof("mesh router stopping")
		g.router.Stop()
	}()

	g.runSeeding()

	return nil
}

func (g *MeshGossiper) runSeeding() {
	for {
		glog.V(2).Infof("Querying for seeds")

		seeds, err := g.seeds.GetSeeds()
		if err != nil {
			glog.Warningf("error getting seeds: %v", err)
			time.Sleep(1 * time.Minute)
			continue
		}

		glog.Infof("Got seeds: %s", seeds)
		// TODO: Include ourselves?  Exclude ourselves?

		removeOthers := false
		errors := g.router.ConnectionMaker.InitiateConnections(seeds, removeOthers)

		if len(errors) != 0 {
			for _, err := range errors {
				glog.Infof("error connecting to seeds: %v", err)
			}

			time.Sleep(1 * time.Minute)
			continue
		}

		glog.V(2).Infof("Seeding successful")

		// Reseed periodically, just in case of partitions
		// TODO: Make it so that only one node polls, or at least statistically get close
		time.Sleep(60 * time.Minute)
	}
}

func (g *MeshGossiper) Snapshot() *gossip.GossipStateSnapshot {
	return g.peer.snapshot()
}

func (g *MeshGossiper) UpdateValues(removeKeys []string, putEntries map[string]string) error {
	glog.V(2).Infof("UpdateValues: remove=%s, put=%s", removeKeys, putEntries)
	return g.peer.updateValues(removeKeys, putEntries)
}
<commit_msg>add err to log<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package mesh

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/golang/glog"
	"github.com/weaveworks/mesh"
	"k8s.io/kops/protokube/pkg/gossip"
)

type MeshGossiper struct {
	seeds gossip.SeedProvider

	router *mesh.Router
	peer   *peer

	version uint64

	lastSnapshot *gossip.GossipStateSnapshot
}

func NewMeshGossiper(listen string, channelName string, nodeName string, password []byte, seeds gossip.SeedProvider) (*MeshGossiper, error) {

	connLimit := 64
	gossipDnsConnLimit := os.Getenv("GOSSIP_DNS_CONN_LIMIT")
	if gossipDnsConnLimit != "" {
		limit, err := strconv.Atoi(gossipDnsConnLimit)
		if err != nil {
			return nil, fmt.Errorf("cannot parse env GOSSIP_DNS_CONN_LIMIT value: %v, err:%v", gossipDnsConnLimit, err)
		}
		connLimit = limit
	}

	glog.Infof("gossip dns connection limit is:%d", connLimit)

	meshConfig := mesh.Config{
		ProtocolMinVersion: mesh.ProtocolMinVersion,
		Password:           password,
		ConnLimit:          connLimit,
		PeerDiscovery:      true,
		//TrustedSubnets:     []*net.IPNet{},
	}

	{
		host, portString, err := net.SplitHostPort(listen)
		if err != nil {
			return nil, fmt.Errorf("cannot parse -listen flag: %v", listen)
		}
		port, err := strconv.Atoi(portString)
		if err != nil {
			return nil, fmt.Errorf("cannot parse -listen flag: %v", listen)
		}
		meshConfig.Host = host
		meshConfig.Port = port
	}

	meshName, err := mesh.PeerNameFromUserInput(nodeName)
	if err != nil {
		return nil, fmt.Errorf("error parsing peer name: %v", err)
	}

	nickname := nodeName
	logger := &glogLogger{}
	router := mesh.NewRouter(meshConfig, meshName, nickname, mesh.NullOverlay{}, logger)

	peer := newPeer(meshName)
	gossip := router.NewGossip(channelName, peer)
	peer.register(gossip)

	gossiper := &MeshGossiper{
		seeds:  seeds,
		router: router,
		peer:   peer,
	}
	return gossiper, nil
}

func (g *MeshGossiper) Start() error {
	//glog.Infof("mesh router starting (%s)", *meshListen)
	g.router.Start()

	defer func() {
		glog.Infof("mesh router stopping")
		g.router.Stop()
	}()

	g.runSeeding()

	return nil
}

func (g *MeshGossiper) runSeeding() {
	for {
		glog.V(2).Infof("Querying for seeds")

		seeds, err := g.seeds.GetSeeds()
		if err != nil {
			glog.Warningf("error getting seeds: %v", err)
			time.Sleep(1 * time.Minute)
			continue
		}

		glog.Infof("Got seeds: %s", seeds)
		// TODO: Include ourselves?  Exclude ourselves?

		removeOthers := false
		errors := g.router.ConnectionMaker.InitiateConnections(seeds, removeOthers)

		if len(errors) != 0 {
			for _, err := range errors {
				glog.Infof("error connecting to seeds: %v", err)
			}

			time.Sleep(1 * time.Minute)
			continue
		}

		glog.V(2).Infof("Seeding successful")

		// Reseed periodically, just in case of partitions
		// TODO: Make it so that only one node polls, or at least statistically get close
		time.Sleep(60 * time.Minute)
	}
}

func (g *MeshGossiper) Snapshot() *gossip.GossipStateSnapshot {
	return g.peer.snapshot()
}

func (g *MeshGossiper) UpdateValues(removeKeys []string, putEntries map[string]string) error {
	glog.V(2).Infof("UpdateValues: remove=%s, put=%s", removeKeys, putEntries)
	return g.peer.updateValues(removeKeys, putEntries)
}
<|endoftext|>


<commit_before>package ledis

import (
	"github.com/garyburd/redigo/redis"
	"os"
	"sync"
	"testing"
)

var testAppOnce sync.Once
var testApp *App

var testPool *redis.Pool

func newTestRedisPool() {
	f := func() (redis.Conn, error) {
		c, err := redis.Dial("tcp", "127.0.0.1:6380")
		if err != nil {
			return nil, err
		}

		return c, nil
	}

	testPool = redis.NewPool(f, 4)
}

func getTestConn() redis.Conn {
	return testPool.Get()
}

func startTestApp() {
	f := func() {
		newTestRedisPool()

		os.RemoveAll("/tmp/testdb")

		var d = []byte(`
            {
                "addr" : "127.0.0.1:6380",
                "leveldb" :
                {
                    "path" : "/tmp/testdb",
                    "compression":true,
                    "block_size" : 32768,
                    "write_buffer_size" : 2097152,
                    "cache_size" : 20971520
                }
            }
            `)

		cfg, err := NewConfig(d)
		if err != nil {
			println(err.Error())
			panic(err)
		}

		testApp, err = NewApp(cfg)
		if err != nil {
			println(err.Error())
			panic(err)
		}

		go testApp.Run()
	}

	testAppOnce.Do(f)
}

func TestApp(t *testing.T) {
	startTestApp()
}
<commit_msg>ledis test change port to 16380<commit_after>package ledis

import (
	"github.com/garyburd/redigo/redis"
	"os"
	"sync"
	"testing"
)

var testAppOnce sync.Once
var testApp *App

var testPool *redis.Pool

func newTestRedisPool() {
	f := func() (redis.Conn, error) {
		c, err := redis.Dial("tcp", "127.0.0.1:16380")
		if err != nil {
			return nil, err
		}

		return c, nil
	}

	testPool = redis.NewPool(f, 4)
}

func getTestConn() redis.Conn {
	return testPool.Get()
}

func startTestApp() {
	f := func() {
		newTestRedisPool()

		os.RemoveAll("/tmp/testdb")

		var d = []byte(`
            {
                "addr" : "127.0.0.1:16380",
                "leveldb" :
                {
                    "path" : "/tmp/testdb",
                    "compression":true,
                    "block_size" : 32768,
                    "write_buffer_size" : 2097152,
                    "cache_size" : 20971520
                }
            }
            `)

		cfg, err := NewConfig(d)
		if err != nil {
			println(err.Error())
			panic(err)
		}

		testApp, err = NewApp(cfg)
		if err != nil {
			println(err.Error())
			panic(err)
		}

		go testApp.Run()
	}

	testAppOnce.Do(f)
}

func TestApp(t *testing.T) {
	startTestApp()
}
<|endoftext|>


<commit_before>package qingcloud

import (
	"fmt"

	"github.com/hashicorp/terraform/helper/schema"
	qc "github.com/yunify/qingcloud-sdk-go/service"
)

func resourceQingcloudVpc() *schema.Resource {
	return &schema.Resource{
		Create: resourceQingcloudVpcCreate,
		Read:   resourceQingcloudVpcRead,
		Update: resourceQingcloudVpcUpdate,
		Delete: resourceQingcloudVpcDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "The name of Vpc",
			},
			"type": &schema.Schema{
				Type:         schema.TypeInt,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: withinArrayInt(0, 1, 2),
				Description: "Type of Vpc: 0 - medium,1 - small,2 - large,default 1	",
			},
			"vpc_network": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: withinArrayString("192.168.0.0/16", "172.16.0.0/16", "172.17.0.0/16",
					"172.18.0.0/16", "172.19.0.0/16", "172.20.0.0/16", "172.21.0.0/16", "172.22.0.0/16",
					"172.23.0.0/16", "172.24.0.0/16", "172.25.0.0/16"),
				Description: "Network address range of vpc.",
			},
			"tag_ids": &schema.Schema{
				Type:        schema.TypeSet,
				Optional:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "The tag ids' id used by the vpc",
			},
			"tag_names": &schema.Schema{
				Type:        schema.TypeSet,
				Computed:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "compute by tag ids ",
			},
			"eip_id": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "The eip's id used by the vpc",
			},
			"security_group_id": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				Description: "The security group's id used by the vpc",
			},
			"description": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "The description of vpc",
			},
			"private_ip": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "The private ip of vpc",
			},
			"public_ip": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "The public ip of vpc",
			},
		},
	}
}

// resourceQingcloudRouterCreate
func resourceQingcloudVpcCreate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	input := new(qc.CreateRoutersInput)
	input.RouterName = qc.String(d.Get("name").(string))
	if d.Get("name").(string) != "" {
		input.RouterName = qc.String(d.Get("name").(string))
	}
	input.RouterType = qc.Int(d.Get("type").(int))
	if d.Get("vpc_network").(string) != "" {
		input.VpcNetwork = qc.String(d.Get("vpc_network").(string))
	}
	if d.Get("security_group_id").(string) != "" {
		input.SecurityGroup = qc.String(d.Get("security_group_id").(string))
	}
	input.Count = qc.Int(1)
	output, err := clt.CreateRouters(input)
	if err != nil {
		return fmt.Errorf("Error create router: %s", err.Error())
	}
	if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
		return fmt.Errorf("Error create router: %s", *output.Message)
	}
	d.SetId(qc.StringValue(output.Routers[0]))
	_, err = RouterTransitionStateRefresh(clt, d.Id())
	if err != nil {
		return fmt.Errorf("Error waiting for router (%s) to start: %s", d.Id(), err.Error())
	}
	return resourceQingcloudVpcUpdate(d, meta)
}

func resourceQingcloudVpcRead(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	input := new(qc.DescribeRoutersInput)
	input.Routers = []*string{qc.String(d.Id())}
	input.Verbose = qc.Int(1)
	output, err := clt.DescribeRouters(input)
	if err != nil {
		return fmt.Errorf("Error describe router: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error describe router: %s", *output.Message)
	}
	if len(output.RouterSet) == 0 {
		d.SetId("")
		return nil
	}
	rtr := output.RouterSet[0]
	d.Set("name", qc.StringValue(rtr.RouterName))
	d.Set("type", qc.IntValue(rtr.RouterType))
	d.Set("security_group_id", qc.StringValue(rtr.SecurityGroupID))
	d.Set("description", qc.StringValue(rtr.Description))
	d.Set("private_ip", qc.StringValue(rtr.PrivateIP))
	d.Set("eip_id", qc.StringValue(rtr.EIP.EIPID))
	d.Set("public_ip", qc.StringValue(rtr.EIP.EIPAddr))
	return nil
}

func resourceQingcloudVpcUpdate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	if _, err := RouterTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	waitRouterLease(d, meta)
	d.Partial(true)
	if err := modifyRouterAttributes(d, meta); err != nil {
		return err
	}
	d.SetPartial("name")
	d.SetPartial("description")
	if d.HasChange("eip_id") {
		input := new(qc.UpdateRoutersInput)
		input.Routers = []*string{qc.String(d.Id())}
		output, err := clt.UpdateRouters(input)
		if err != nil {
			return fmt.Errorf("Error update router: %s", err.Error())
		}
		if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
			return fmt.Errorf("Error update router: %s", *output.Message)
		}
		_, err = RouterTransitionStateRefresh(clt, d.Id())
		if err != nil {
			return fmt.Errorf("Error waiting for router (%s) to start: %s", d.Id(), err.Error())
		}
	}
	d.SetPartial("eip_id")
	if d.HasChange("security_group_id") && !d.IsNewResource() {
		sgClt := meta.(*QingCloudClient).securitygroup
		input := new(qc.ApplySecurityGroupInput)
		input.SecurityGroup = qc.String(d.Get("security_group_id").(string))
		output, err := sgClt.ApplySecurityGroup(input)
		if err != nil {
			return fmt.Errorf("Error apply security group (%s) update %s", *input.SecurityGroup, *output.Message)
		}
	}
	d.SetPartial("security_group_id")
	if err := resourceUpdateTag(d, meta, qingcloudResourceTypeRouter); err != nil {
		return err
	}
	d.SetPartial("tag_ids")
	d.Partial(false)
	return resourceQingcloudRouterRead(d, meta)
}

func resourceQingcloudVpcDelete(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	if _, err := RouterTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	waitRouterLease(d, meta)
	input := new(qc.DeleteRoutersInput)
	input.Routers = []*string{qc.String(d.Id())}
	output, err := clt.DeleteRouters(input)
	if err != nil {
		return fmt.Errorf("Error delete router: %s", err)
	}
	if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
		return fmt.Errorf("Error delete router: %s", *output.Message)
	}
	if _, err := RouterTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	d.SetId("")
	return nil
}
<commit_msg>format description<commit_after>package qingcloud

import (
	"fmt"

	"github.com/hashicorp/terraform/helper/schema"
	qc "github.com/yunify/qingcloud-sdk-go/service"
)

func resourceQingcloudVpc() *schema.Resource {
	return &schema.Resource{
		Create: resourceQingcloudVpcCreate,
		Read:   resourceQingcloudVpcRead,
		Update: resourceQingcloudVpcUpdate,
		Delete: resourceQingcloudVpcDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "The name of Vpc",
			},
			"type": &schema.Schema{
				Type:         schema.TypeInt,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: withinArrayInt(0, 1, 2),
				Description: "Type of Vpc: 0 - medium, 1 - small, 2 - large, default 1	",
			},
			"vpc_network": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: withinArrayString("192.168.0.0/16", "172.16.0.0/16", "172.17.0.0/16",
					"172.18.0.0/16", "172.19.0.0/16", "172.20.0.0/16", "172.21.0.0/16", "172.22.0.0/16",
					"172.23.0.0/16", "172.24.0.0/16", "172.25.0.0/16"),
				Description: "Network address range of vpc.",
			},
			"tag_ids": &schema.Schema{
				Type:        schema.TypeSet,
				Optional:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "The tag ids' id used by the vpc",
			},
			"tag_names": &schema.Schema{
				Type:        schema.TypeSet,
				Computed:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "compute by tag ids ",
			},
			"eip_id": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "The eip's id used by the vpc",
			},
			"security_group_id": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				Description: "The security group's id used by the vpc",
			},
			"description": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "The description of vpc",
			},
			"private_ip": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "The private ip of vpc",
			},
			"public_ip": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "The public ip of vpc",
			},
		},
	}
}

// resourceQingcloudRouterCreate
func resourceQingcloudVpcCreate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	input := new(qc.CreateRoutersInput)
	input.RouterName = qc.String(d.Get("name").(string))
	if d.Get("name").(string) != "" {
		input.RouterName = qc.String(d.Get("name").(string))
	}
	input.RouterType = qc.Int(d.Get("type").(int))
	if d.Get("vpc_network").(string) != "" {
		input.VpcNetwork = qc.String(d.Get("vpc_network").(string))
	}
	if d.Get("security_group_id").(string) != "" {
		input.SecurityGroup = qc.String(d.Get("security_group_id").(string))
	}
	input.Count = qc.Int(1)
	output, err := clt.CreateRouters(input)
	if err != nil {
		return fmt.Errorf("Error create router: %s", err.Error())
	}
	if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
		return fmt.Errorf("Error create router: %s", *output.Message)
	}
	d.SetId(qc.StringValue(output.Routers[0]))
	_, err = RouterTransitionStateRefresh(clt, d.Id())
	if err != nil {
		return fmt.Errorf("Error waiting for router (%s) to start: %s", d.Id(), err.Error())
	}
	return resourceQingcloudVpcUpdate(d, meta)
}

func resourceQingcloudVpcRead(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	input := new(qc.DescribeRoutersInput)
	input.Routers = []*string{qc.String(d.Id())}
	input.Verbose = qc.Int(1)
	output, err := clt.DescribeRouters(input)
	if err != nil {
		return fmt.Errorf("Error describe router: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error describe router: %s", *output.Message)
	}
	if len(output.RouterSet) == 0 {
		d.SetId("")
		return nil
	}
	rtr := output.RouterSet[0]
	d.Set("name", qc.StringValue(rtr.RouterName))
	d.Set("type", qc.IntValue(rtr.RouterType))
	d.Set("security_group_id", qc.StringValue(rtr.SecurityGroupID))
	d.Set("description", qc.StringValue(rtr.Description))
	d.Set("private_ip", qc.StringValue(rtr.PrivateIP))
	d.Set("eip_id", qc.StringValue(rtr.EIP.EIPID))
	d.Set("public_ip", qc.StringValue(rtr.EIP.EIPAddr))
	return nil
}

func resourceQingcloudVpcUpdate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	if _, err := RouterTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	waitRouterLease(d, meta)
	d.Partial(true)
	if err := modifyRouterAttributes(d, meta); err != nil {
		return err
	}
	d.SetPartial("name")
	d.SetPartial("description")
	if d.HasChange("eip_id") {
		input := new(qc.UpdateRoutersInput)
		input.Routers = []*string{qc.String(d.Id())}
		output, err := clt.UpdateRouters(input)
		if err != nil {
			return fmt.Errorf("Error update router: %s", err.Error())
		}
		if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
			return fmt.Errorf("Error update router: %s", *output.Message)
		}
		_, err = RouterTransitionStateRefresh(clt, d.Id())
		if err != nil {
			return fmt.Errorf("Error waiting for router (%s) to start: %s", d.Id(), err.Error())
		}
	}
	d.SetPartial("eip_id")
	if d.HasChange("security_group_id") && !d.IsNewResource() {
		sgClt := meta.(*QingCloudClient).securitygroup
		input := new(qc.ApplySecurityGroupInput)
		input.SecurityGroup = qc.String(d.Get("security_group_id").(string))
		output, err := sgClt.ApplySecurityGroup(input)
		if err != nil {
			return fmt.Errorf("Error apply security group (%s) update %s", *input.SecurityGroup, *output.Message)
		}
	}
	d.SetPartial("security_group_id")
	if err := resourceUpdateTag(d, meta, qingcloudResourceTypeRouter); err != nil {
		return err
	}
	d.SetPartial("tag_ids")
	d.Partial(false)
	return resourceQingcloudRouterRead(d, meta)
}

func resourceQingcloudVpcDelete(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).router
	if _, err := RouterTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	waitRouterLease(d, meta)
	input := new(qc.DeleteRoutersInput)
	input.Routers = []*string{qc.String(d.Id())}
	output, err := clt.DeleteRouters(input)
	if err != nil {
		return fmt.Errorf("Error delete router: %s", err)
	}
	if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
		return fmt.Errorf("Error delete router: %s", *output.Message)
	}
	if _, err := RouterTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	d.SetId("")
	return nil
}
<|endoftext|>


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

package discoverspaces

import (
	"github.com/juju/errors"
	"github.com/juju/juju/api/discoverspaces"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/worker"
	"github.com/juju/loggo"
	"github.com/juju/names"
	"launchpad.net/tomb"
)

var logger = loggo.GetLogger("juju.discoverspaces")

type discoverspacesWorker struct {
	api      *discoverspaces.API
	tomb     tomb.Tomb
	observer *worker.EnvironObserver
}

// NewWorker returns a worker
func NewWorker(api *discoverspaces.API) worker.Worker {
	dw := &discoverspacesWorker{
		api: api,
	}
	go func() {
		defer dw.tomb.Done()
		dw.tomb.Kill(dw.loop())
	}()
	return dw
}

func (dw *discoverspacesWorker) Kill() {
	dw.tomb.Kill(nil)
}

func (dw *discoverspacesWorker) Wait() error {
	return dw.tomb.Wait()
}

func (dw *discoverspacesWorker) loop() (err error) {
	dw.observer, err = worker.NewEnvironObserver(dw.api)
	if err != nil {
		return err
	}
	defer func() {
		obsErr := worker.Stop(dw.observer)
		if err == nil {
			err = obsErr
		}
	}()
	environ := dw.observer.Environ()
	networkingEnviron, ok := environs.SupportsNetworking(environ)

	if ok {
		err = dw.handleSubnets(networkingEnviron)
		if err != nil {
			return errors.Trace(err)
		}
	}

	// TODO(mfoord): we'll have a watcher here checking if we need to
	// update the spaces/subnets definition.
	dying := u.tomb.Dying()
	for {
		select {
		case <-dying:
			return nil
		}
	}
	return nil
}

func (dw *discoverspacesWorker) handleSubnets(env environs.NetworkingEnviron) error {
	ok, err := env.SupportsSpaceDiscovery()
	if err != nil {
		return errors.Trace(err)
	}
	if !ok {
		// Nothing to do.
		return nil
	}
	providerSpaces, err := env.Spaces()
	if err != nil {
		return errors.Trace(err)
	}
	listSpacesResult, err := dw.api.ListSpaces()
	if err != nil {
		return errors.Trace(err)
	}
	stateSpaceMap := make(map[string]params.ProviderSpace)
	for _, space := range listSpacesResult.Results {
		stateSpaceMap[space.ProviderId] = space
	}

	// TODO(mfoord): we also need to attempt to delete spaces and subnets
	// that no longer exist, so long as they're not in use.
	for _, space := range providerSpaces {
		_, ok := stateSpaceMap[space.Name]
		if !ok {
			// We need to create the space.
			// XXX in the apiserver the name should be generated and
			// IsPublic set to false.
			args := params.CreateSpacesParams{}
			_, err := dw.api.CreateSpaces(args)
			if err != nil {
				return errors.Trace(err)
			}
		}
		// TODO(mfoord): currently no way of removing subnets, or
		// changing the space they're in, so we can only add ones we
		// don't already know about.
		for _, subnet := range space.Subnets {
			spaceTag, err := names.ParseSpaceTag(space.Name)
			if err != nil {
				return errors.Trace(err)
			}
			subnetTag, err := names.ParseSubnetTag(subnet.CIDR)
			if err != nil {
				return errors.Trace(err)
			}

			args := params.AddSubnetsParams{
				Subnets: []params.AddSubnetParams{{
					SubnetTag:        subnetTag.String(),
					SubnetProviderId: string(subnet.ProviderId),
					SpaceTag:         spaceTag.String(),
					Zones:            subnet.AvailabilityZones,
				}}}
			_, err = dw.api.AddSubnets(args)
			if err != nil {
				return errors.Trace(err)
			}
		}
	}
	return nil
}
<commit_msg>Typo fix<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package discoverspaces

import (
	"github.com/juju/errors"
	"github.com/juju/juju/api/discoverspaces"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/worker"
	"github.com/juju/loggo"
	"github.com/juju/names"
	"launchpad.net/tomb"
)

var logger = loggo.GetLogger("juju.discoverspaces")

type discoverspacesWorker struct {
	api      *discoverspaces.API
	tomb     tomb.Tomb
	observer *worker.EnvironObserver
}

// NewWorker returns a worker
func NewWorker(api *discoverspaces.API) worker.Worker {
	dw := &discoverspacesWorker{
		api: api,
	}
	go func() {
		defer dw.tomb.Done()
		dw.tomb.Kill(dw.loop())
	}()
	return dw
}

func (dw *discoverspacesWorker) Kill() {
	dw.tomb.Kill(nil)
}

func (dw *discoverspacesWorker) Wait() error {
	return dw.tomb.Wait()
}

func (dw *discoverspacesWorker) loop() (err error) {
	dw.observer, err = worker.NewEnvironObserver(dw.api)
	if err != nil {
		return err
	}
	defer func() {
		obsErr := worker.Stop(dw.observer)
		if err == nil {
			err = obsErr
		}
	}()
	environ := dw.observer.Environ()
	networkingEnviron, ok := environs.SupportsNetworking(environ)

	if ok {
		err = dw.handleSubnets(networkingEnviron)
		if err != nil {
			return errors.Trace(err)
		}
	}

	// TODO(mfoord): we'll have a watcher here checking if we need to
	// update the spaces/subnets definition.
	dying := dw.tomb.Dying()
	for {
		select {
		case <-dying:
			return nil
		}
	}
	return nil
}

func (dw *discoverspacesWorker) handleSubnets(env environs.NetworkingEnviron) error {
	ok, err := env.SupportsSpaceDiscovery()
	if err != nil {
		return errors.Trace(err)
	}
	if !ok {
		// Nothing to do.
		return nil
	}
	providerSpaces, err := env.Spaces()
	if err != nil {
		return errors.Trace(err)
	}
	listSpacesResult, err := dw.api.ListSpaces()
	if err != nil {
		return errors.Trace(err)
	}
	stateSpaceMap := make(map[string]params.ProviderSpace)
	for _, space := range listSpacesResult.Results {
		stateSpaceMap[space.ProviderId] = space
	}

	// TODO(mfoord): we also need to attempt to delete spaces and subnets
	// that no longer exist, so long as they're not in use.
	for _, space := range providerSpaces {
		_, ok := stateSpaceMap[space.Name]
		if !ok {
			// We need to create the space.
			// XXX in the apiserver the name should be generated and
			// IsPublic set to false.
			args := params.CreateSpacesParams{}
			_, err := dw.api.CreateSpaces(args)
			if err != nil {
				return errors.Trace(err)
			}
		}
		// TODO(mfoord): currently no way of removing subnets, or
		// changing the space they're in, so we can only add ones we
		// don't already know about.
		for _, subnet := range space.Subnets {
			spaceTag, err := names.ParseSpaceTag(space.Name)
			if err != nil {
				return errors.Trace(err)
			}
			subnetTag, err := names.ParseSubnetTag(subnet.CIDR)
			if err != nil {
				return errors.Trace(err)
			}

			args := params.AddSubnetsParams{
				Subnets: []params.AddSubnetParams{{
					SubnetTag:        subnetTag.String(),
					SubnetProviderId: string(subnet.ProviderId),
					SpaceTag:         spaceTag.String(),
					Zones:            subnet.AvailabilityZones,
				}}}
			_, err = dw.api.AddSubnets(args)
			if err != nil {
				return errors.Trace(err)
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package rabbitsmpp

import (
	"encoding/json"
	"errors"
	"strconv"
	"sync"

	"github.com/cenk/backoff"
	"github.com/streadway/amqp"
)

var (
	errAlreadyBound = errors.New("already bound")
	errClientClosed = errors.New("closed client")
	errMaxBindretry = errors.New("max bind retry")
)

// PublisherClient is same as Client interface but also support Rebind operation
type PublisherClient interface {
	Client
	ReBind() (PublisherClient, chan *amqp.Error, error)
}

type publisherClient struct {
	sm *sync.Mutex

	config   Config
	ampqConn *amqp.Connection
	bound    bool

	closed bool
}

func newPublisherClient(conf Config) (*publisherClient, error) {
	var m sync.Mutex

	return &publisherClient{
		sm:     &m,
		config: conf,
	}, nil
}

func (c *publisherClient) Bind() (chan *amqp.Error, error) {
	c.sm.Lock()
	defer c.sm.Unlock()

	if c.bound {
		return nil, errAlreadyBound
	}

	if c.closed {
		return nil, errClientClosed
	}

	conn, err := amqp.Dial(c.config.URL)
	if err != nil {
		return nil, err
	}

	errors := make(chan *amqp.Error)
	errChan := conn.NotifyClose(errors)
	c.ampqConn = conn

	c.bound = true
	return errChan, nil
}

func (c *publisherClient) Channel() (Channel, error) {
	c.sm.Lock()
	defer c.sm.Unlock()

	if c.closed {
		return nil, errClientClosed
	}

	return c.ampqConn.Channel()
}

func (c *publisherClient) ReBind() (PublisherClient, chan *amqp.Error, error) {
	c.sm.Lock()
	defer c.sm.Unlock()

	ticker := backoff.NewTicker(backoff.NewExponentialBackOff())
	for _ = range ticker.C {
		newClient, err := newPublisherClient(c.config)
		if err != nil {
			continue
		}

		errChan, err := newClient.Bind()
		if err != nil {
			continue
		}

		// stop the ticker
		ticker.Stop()
		return newClient, errChan, nil
	}

	return nil, nil, errClientClosed

}

func (c *publisherClient) Close() error {
	c.sm.Lock()
	defer c.sm.Unlock()

	if c.closed {
		return nil
	}

	c.closed = true
	c.ampqConn.Close()
	return nil
}

func (c *publisherClient) QueueName() string {
	c.sm.Lock()
	defer c.sm.Unlock()

	return c.config.QueueName
}

// Publisher is interface for publishing data to the queues
type Publisher interface {
	Publish(Job) error
	Close() error
}

func runWithRecovery(runCmd func() error, onError func() error) error {
	if err := runCmd(); err == nil {
		return nil
	}

	if err := onError(); err != nil {
		return err
	}

	return runCmd()

}

type publisher struct {
	m        *sync.Mutex
	client   PublisherClient
	sendChan Channel
}

// NewPublisher creates a new publisher with direct exchange
func NewPublisher(conf Config) (Publisher, error) {
	pubClient, err := newPublisherClient(conf)
	if err != nil {
		return nil, err
	}

	return newPublisherWithClient(pubClient)
}

func newPublisherWithClient(client PublisherClient) (Publisher, error) {
	var m sync.Mutex

	publisher := &publisher{
		m:      &m,
		client: client,
	}

	// setup all the connections here
	if err := publisher.reset(false); err != nil {
		return nil, err
	}

	return publisher, nil
}

func (p *publisher) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	p.sendChan.Close()
	p.client.Close()
	return nil
}

func (p *publisher) queueSetup() error {
	ch, err := p.client.Channel()
	if err != nil {
		p.Close()
		return err
	}

	p.sendChan = ch

	_, err = ch.QueueDeclare(
		p.client.QueueName(), // name
		true,                 // durable
		false,                // delete when unused
		false,                // exclusive
		false,                // no-wait
		nil,                  // arguments
	)
	if err != nil {
		p.Close()
		return err
	}

	return nil
}

// Publish sends the Job to the rabbitmq if the underlying connection needs re-binding it is done automagically
func (p *publisher) Publish(j Job) error {
	p.m.Lock()
	defer p.m.Unlock()

	bodyBytes, err := json.Marshal(j)
	if err != nil {
		return err
	}

	cmd := func() error {
		return p.sendChan.Publish(
			"",                   // exchange
			p.client.QueueName(), // routing key
			false,                // mandatory
			false,                // immediate
			amqp.Publishing{
				ContentType: "application/json",
				Body:        bodyBytes,
			})
	}

	return runWithRecovery(cmd, func() error {
		return p.reset(true)
	})
}

func (p *publisher) reset(rebind bool) error {

	if !rebind {
		_, err := p.client.Bind()
		if err != nil {
			return err
		}
	} else {
		client, _, err := p.client.ReBind()
		if err != nil {
			return err
		}

		p.client = client
	}

	// setup the queues
	return p.queueSetup()
}

type delayedPublisher struct {
	m        sync.Mutex
	client   PublisherClient
	sendChan Channel

	delayExchange string
	delayMS       int
}

// NewDelayedPublisher works the same way as the normal publisher except it pushes
// its messages are sent to a delayExchange which keeps them there for delayMS time and then redirects
// them to the conf.QueueName so consumers can start consuming them
func NewDelayedPublisher(conf Config, delayExchange string, delayMS int) (Publisher, error) {
	client, err := newPublisherClient(conf)
	if err != nil {
		return nil, err
	}

	return newDelayedPublisherWithClient(client, delayExchange, delayMS)
}

func newDelayedPublisherWithClient(client PublisherClient, delayExchange string, delayMS int) (Publisher, error) {
	var m sync.Mutex

	publisher := &delayedPublisher{
		m:             m,
		client:        client,
		delayExchange: delayExchange,
		delayMS:       delayMS,
	}

	// setup all the connections here
	if err := publisher.reset(false); err != nil {
		return nil, err
	}

	return publisher, nil
}

func (p *delayedPublisher) reset(rebind bool) error {

	if !rebind {
		_, err := p.client.Bind()
		if err != nil {
			return err
		}
	} else {
		client, _, err := p.client.ReBind()
		if err != nil {
			return err
		}

		p.client = client
	}

	// setup the queues
	if err := p.queueSetup(); err != nil {
		return err
	}

	return nil
}

func (p *delayedPublisher) queueSetup() error {
	ch, err := p.client.Channel()
	if err != nil {
		p.Close()
		return err
	}

	p.sendChan = ch

	// Create a delayed exchange
	err = ch.ExchangeDeclare(
		p.delayExchange,
		"x-delayed-message",
		true,
		false,
		false,
		true,
		amqp.Table{
			"x-delayed-type": "direct",
		},
	)

	if err != nil {
		return err
	}

	q, err := ch.QueueDeclare(
		p.client.QueueName(), // name
		true,                 // durable
		false,                // delete when unused
		false,                // exclusive
		false,                // no-wait
		nil,                  // arguments
	)
	if err != nil {
		return err
	}

	err = ch.QueueBind(
		q.Name,               // queue name
		p.client.QueueName(), // routing key
		p.delayExchange,      // exchange
		false,
		nil)

	if err != nil {
		return err
	}

	return nil
}

func (p *delayedPublisher) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	p.sendChan.Close()
	p.client.Close()
	return nil
}

func (p *delayedPublisher) Publish(j Job) error {
	p.m.Lock()
	defer p.m.Unlock()

	bodyBytes, err := json.Marshal(j)
	if err != nil {
		return err
	}

	cmd := func() error {
		return p.sendChan.Publish(
			p.delayExchange,      // exchange
			p.client.QueueName(), // routing key
			false,                // mandatory
			false,                // immediate
			amqp.Publishing{
				Headers: amqp.Table{
					"x-delay": strconv.Itoa(p.delayMS),
				},
				ContentType: "application/json",
				Body:        bodyBytes,
			})
	}

	return runWithRecovery(cmd, func() error {
		return p.reset(true)
	})
}
<commit_msg>don't copy the mutex<commit_after>package rabbitsmpp

import (
	"encoding/json"
	"errors"
	"strconv"
	"sync"

	"github.com/cenk/backoff"
	"github.com/streadway/amqp"
)

var (
	errAlreadyBound = errors.New("already bound")
	errClientClosed = errors.New("closed client")
	errMaxBindretry = errors.New("max bind retry")
)

// PublisherClient is same as Client interface but also support Rebind operation
type PublisherClient interface {
	Client
	ReBind() (PublisherClient, chan *amqp.Error, error)
}

type publisherClient struct {
	sm *sync.Mutex

	config   Config
	ampqConn *amqp.Connection
	bound    bool

	closed bool
}

func newPublisherClient(conf Config) (*publisherClient, error) {
	var m sync.Mutex

	return &publisherClient{
		sm:     &m,
		config: conf,
	}, nil
}

func (c *publisherClient) Bind() (chan *amqp.Error, error) {
	c.sm.Lock()
	defer c.sm.Unlock()

	if c.bound {
		return nil, errAlreadyBound
	}

	if c.closed {
		return nil, errClientClosed
	}

	conn, err := amqp.Dial(c.config.URL)
	if err != nil {
		return nil, err
	}

	errors := make(chan *amqp.Error)
	errChan := conn.NotifyClose(errors)
	c.ampqConn = conn

	c.bound = true
	return errChan, nil
}

func (c *publisherClient) Channel() (Channel, error) {
	c.sm.Lock()
	defer c.sm.Unlock()

	if c.closed {
		return nil, errClientClosed
	}

	return c.ampqConn.Channel()
}

func (c *publisherClient) ReBind() (PublisherClient, chan *amqp.Error, error) {
	c.sm.Lock()
	defer c.sm.Unlock()

	ticker := backoff.NewTicker(backoff.NewExponentialBackOff())
	for _ = range ticker.C {
		newClient, err := newPublisherClient(c.config)
		if err != nil {
			continue
		}

		errChan, err := newClient.Bind()
		if err != nil {
			continue
		}

		// stop the ticker
		ticker.Stop()
		return newClient, errChan, nil
	}

	return nil, nil, errClientClosed

}

func (c *publisherClient) Close() error {
	c.sm.Lock()
	defer c.sm.Unlock()

	if c.closed {
		return nil
	}

	c.closed = true
	c.ampqConn.Close()
	return nil
}

func (c *publisherClient) QueueName() string {
	c.sm.Lock()
	defer c.sm.Unlock()

	return c.config.QueueName
}

// Publisher is interface for publishing data to the queues
type Publisher interface {
	Publish(Job) error
	Close() error
}

func runWithRecovery(runCmd func() error, onError func() error) error {
	if err := runCmd(); err == nil {
		return nil
	}

	if err := onError(); err != nil {
		return err
	}

	return runCmd()

}

type publisher struct {
	m        *sync.Mutex
	client   PublisherClient
	sendChan Channel
}

// NewPublisher creates a new publisher with direct exchange
func NewPublisher(conf Config) (Publisher, error) {
	pubClient, err := newPublisherClient(conf)
	if err != nil {
		return nil, err
	}

	return newPublisherWithClient(pubClient)
}

func newPublisherWithClient(client PublisherClient) (Publisher, error) {
	var m sync.Mutex

	publisher := &publisher{
		m:      &m,
		client: client,
	}

	// setup all the connections here
	if err := publisher.reset(false); err != nil {
		return nil, err
	}

	return publisher, nil
}

func (p *publisher) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	p.sendChan.Close()
	p.client.Close()
	return nil
}

func (p *publisher) queueSetup() error {
	ch, err := p.client.Channel()
	if err != nil {
		p.Close()
		return err
	}

	p.sendChan = ch

	_, err = ch.QueueDeclare(
		p.client.QueueName(), // name
		true,                 // durable
		false,                // delete when unused
		false,                // exclusive
		false,                // no-wait
		nil,                  // arguments
	)
	if err != nil {
		p.Close()
		return err
	}

	return nil
}

// Publish sends the Job to the rabbitmq if the underlying connection needs re-binding it is done automagically
func (p *publisher) Publish(j Job) error {
	p.m.Lock()
	defer p.m.Unlock()

	bodyBytes, err := json.Marshal(j)
	if err != nil {
		return err
	}

	cmd := func() error {
		return p.sendChan.Publish(
			"",                   // exchange
			p.client.QueueName(), // routing key
			false,                // mandatory
			false,                // immediate
			amqp.Publishing{
				ContentType: "application/json",
				Body:        bodyBytes,
			})
	}

	return runWithRecovery(cmd, func() error {
		return p.reset(true)
	})
}

func (p *publisher) reset(rebind bool) error {

	if !rebind {
		_, err := p.client.Bind()
		if err != nil {
			return err
		}
	} else {
		client, _, err := p.client.ReBind()
		if err != nil {
			return err
		}

		p.client = client
	}

	// setup the queues
	return p.queueSetup()
}

type delayedPublisher struct {
	m        *sync.Mutex
	client   PublisherClient
	sendChan Channel

	delayExchange string
	delayMS       int
}

// NewDelayedPublisher works the same way as the normal publisher except it pushes
// its messages are sent to a delayExchange which keeps them there for delayMS time and then redirects
// them to the conf.QueueName so consumers can start consuming them
func NewDelayedPublisher(conf Config, delayExchange string, delayMS int) (Publisher, error) {
	client, err := newPublisherClient(conf)
	if err != nil {
		return nil, err
	}

	return newDelayedPublisherWithClient(client, delayExchange, delayMS)
}

func newDelayedPublisherWithClient(client PublisherClient, delayExchange string, delayMS int) (Publisher, error) {
	var m sync.Mutex

	publisher := &delayedPublisher{
		m:             &m,
		client:        client,
		delayExchange: delayExchange,
		delayMS:       delayMS,
	}

	// setup all the connections here
	if err := publisher.reset(false); err != nil {
		return nil, err
	}

	return publisher, nil
}

func (p *delayedPublisher) reset(rebind bool) error {

	if !rebind {
		_, err := p.client.Bind()
		if err != nil {
			return err
		}
	} else {
		client, _, err := p.client.ReBind()
		if err != nil {
			return err
		}

		p.client = client
	}

	// setup the queues
	if err := p.queueSetup(); err != nil {
		return err
	}

	return nil
}

func (p *delayedPublisher) queueSetup() error {
	ch, err := p.client.Channel()
	if err != nil {
		p.Close()
		return err
	}

	p.sendChan = ch

	// Create a delayed exchange
	err = ch.ExchangeDeclare(
		p.delayExchange,
		"x-delayed-message",
		true,
		false,
		false,
		true,
		amqp.Table{
			"x-delayed-type": "direct",
		},
	)

	if err != nil {
		return err
	}

	q, err := ch.QueueDeclare(
		p.client.QueueName(), // name
		true,                 // durable
		false,                // delete when unused
		false,                // exclusive
		false,                // no-wait
		nil,                  // arguments
	)
	if err != nil {
		return err
	}

	err = ch.QueueBind(
		q.Name,               // queue name
		p.client.QueueName(), // routing key
		p.delayExchange,      // exchange
		false,
		nil)

	if err != nil {
		return err
	}

	return nil
}

func (p *delayedPublisher) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	p.sendChan.Close()
	p.client.Close()
	return nil
}

func (p *delayedPublisher) Publish(j Job) error {
	p.m.Lock()
	defer p.m.Unlock()

	bodyBytes, err := json.Marshal(j)
	if err != nil {
		return err
	}

	cmd := func() error {
		return p.sendChan.Publish(
			p.delayExchange,      // exchange
			p.client.QueueName(), // routing key
			false,                // mandatory
			false,                // immediate
			amqp.Publishing{
				Headers: amqp.Table{
					"x-delay": strconv.Itoa(p.delayMS),
				},
				ContentType: "application/json",
				Body:        bodyBytes,
			})
	}

	return runWithRecovery(cmd, func() error {
		return p.reset(true)
	})
}
<|endoftext|>