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

package platformvm

import (
	"fmt"

	"github.com/ava-labs/avalanchego/database"
	"github.com/ava-labs/avalanchego/database/versiondb"
	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/snow/choices"
	"github.com/ava-labs/avalanchego/snow/consensus/snowman"
	"github.com/ava-labs/avalanchego/vms/components/core"
)

// ProposalBlock is a proposal to change the chain's state.
// A proposal may be to:
// 	1. Advance the chain's timestamp (*AdvanceTimeTx)
//  2. Remove a staker from the staker set (*RewardStakerTx)
//  3. Add a new staker to the set of pending (future) stakers (*AddStakerTx)
// The proposal will be enacted (change the chain's state) if the proposal block
// is accepted and followed by an accepted Commit block
type ProposalBlock struct {
	CommonBlock `serialize:"true"`

	Tx Tx `serialize:"true" json:"tx"`

	// The database that the chain will have if this block's proposal is committed
	onCommitDB *versiondb.Database
	// The database that the chain will have if this block's proposal is aborted
	onAbortDB *versiondb.Database
	// The function to execute if this block's proposal is committed
	onCommitFunc func() error
	// The function to execute if this block's proposal is aborted
	onAbortFunc func() error
}

// Accept implements the snowman.Block interface
func (pb *ProposalBlock) Accept() error {
	pb.SetStatus(choices.Accepted)
	pb.VM.LastAcceptedID = pb.ID()
	return nil
}

// Reject implements the snowman.Block interface
func (pb *ProposalBlock) Reject() error {
	if err := pb.vm.mempool.IssueTx(&pb.Tx); err != nil {
		pb.vm.Ctx.Log.Debug("failed to reissue tx %q due to: %s", pb.Tx.ID(), err)
	}
	return pb.CommonBlock.Reject()
}

// Initialize this block.
// Sets [pb.vm] to [vm] and populates non-serialized fields
// This method should be called when a block is unmarshaled from bytes
func (pb *ProposalBlock) initialize(vm *VM, bytes []byte) error {
	pb.vm = vm
	pb.Block.Initialize(bytes, vm.SnowmanVM)

	unsignedBytes, err := pb.vm.codec.Marshal(&pb.Tx.UnsignedTx)
	if err != nil {
		return fmt.Errorf("failed to marshal unsigned tx: %w", err)
	}
	signedBytes, err := pb.vm.codec.Marshal(&pb.Tx)
	if err != nil {
		return fmt.Errorf("failed to marshal tx: %w", err)
	}
	pb.Tx.Initialize(unsignedBytes, signedBytes)
	return nil
}

// setBaseDatabase sets this block's base database to [db]
func (pb *ProposalBlock) setBaseDatabase(db database.Database) {
	if err := pb.onCommitDB.SetDatabase(db); err != nil {
		pb.vm.Ctx.Log.Error("problem while setting base database: %s", err)
	}
	if err := pb.onAbortDB.SetDatabase(db); err != nil {
		pb.vm.Ctx.Log.Error("problem while setting base database: %s", err)
	}
}

// onCommit should only be called after Verify is called.
// onCommit returns:
//   1. A database that contains the state of the chain assuming this proposal
//      is enacted. (That is, if this block is accepted and followed by an
//      accepted Commit block.)
//   2. A function be be executed when this block's proposal is committed.
//      This function should not write to state.
func (pb *ProposalBlock) onCommit() (*versiondb.Database, func() error) {
	return pb.onCommitDB, pb.onCommitFunc
}

// onAbort should only be called after Verify is called.
// onAbort returns a database that contains the state of the chain assuming this
// block's proposal is rejected. (That is, if this block is accepted and
// followed by an accepted Abort block.)
func (pb *ProposalBlock) onAbort() (*versiondb.Database, func() error) {
	return pb.onAbortDB, pb.onAbortFunc
}

// Verify this block is valid.
//
// The parent block must either be a Commit or an Abort block.
//
// If this block is valid, this function also sets pas.onCommit and pas.onAbort.
func (pb *ProposalBlock) Verify() error {
	tx, ok := pb.Tx.UnsignedTx.(UnsignedProposalTx)
	if !ok {
		return errWrongTxType
	}

	parentIntf := pb.parentBlock()

	// The parent of a proposal block (ie this block) must be a decision block
	parent, ok := parentIntf.(decision)
	if !ok {
		if err := pb.Reject(); err == nil {
			if err := pb.vm.DB.Commit(); err != nil {
				return fmt.Errorf("couldn't commit VM's database: %w", err)
			}
		} else {
			pb.vm.DB.Abort()
		}
		return errInvalidBlockType
	}

	// pdb is the database if this block's parent is accepted
	pdb := parent.onAccept()

	txID := tx.ID()

	var err TxError
	pb.onCommitDB, pb.onAbortDB, pb.onCommitFunc, pb.onAbortFunc, err = tx.SemanticVerify(pb.vm, pdb, &pb.Tx)
	if err != nil {
		pb.vm.droppedTxCache.Put(txID, nil) // cache tx as dropped
		// If this block's transaction proposes to advance the timestamp, the transaction may fail
		// verification now but be valid in the future, so don't (permanently) mark the block as rejected.
		if !err.Temporary() {
			if err := pb.Reject(); err == nil {
				if err := pb.vm.DB.Commit(); err != nil {
					return fmt.Errorf("couldn't commit VM's database: %w", err)
				}
			} else {
				pb.vm.DB.Abort()
			}
		}
		return err
	}

	txBytes := tx.Bytes()
	if err := pb.vm.putTx(pb.onCommitDB, txID, txBytes); err != nil {
		return fmt.Errorf("failed to put tx %s in database: %w", txID, err)
	}
	if err := pb.vm.putStatus(pb.onCommitDB, txID, Committed); err != nil {
		return fmt.Errorf("failed to put status of tx %s: %w", txID, err)
	}

	if err := pb.vm.putTx(pb.onAbortDB, txID, txBytes); err != nil {
		return fmt.Errorf("failed to put tx %s in database: %w", txID, err)
	}
	if err := pb.vm.putStatus(pb.onAbortDB, txID, Aborted); err != nil {
		return fmt.Errorf("failed to put status of tx %s: %w", txID, err)
	}

	pb.vm.currentBlocks[pb.ID().Key()] = pb
	parentIntf.addChild(pb)
	return nil
}

// Options returns the possible children of this block in preferential order.
func (pb *ProposalBlock) Options() ([2]snowman.Block, error) {
	blockID := pb.ID()

	commit, err := pb.vm.newCommitBlock(blockID, pb.Height()+1)
	if err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to create commit block: %w", err)
	}
	abort, err := pb.vm.newAbortBlock(blockID, pb.Height()+1)
	if err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to create abort block: %w", err)
	}

	if err := pb.vm.State.PutBlock(pb.vm.DB, commit); err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to put commit block: %w", err)
	}
	if err := pb.vm.State.PutBlock(pb.vm.DB, abort); err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to put abort block: %w", err)
	}
	if err := pb.vm.DB.Commit(); err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to commit VM's database: %w", err)
	}

	tx, ok := pb.Tx.UnsignedTx.(UnsignedProposalTx)
	if !ok {
		return [2]snowman.Block{}, errWrongTxType
	}

	if tx.InitiallyPrefersCommit(pb.vm) {
		return [2]snowman.Block{commit, abort}, nil
	}
	return [2]snowman.Block{abort, commit}, nil
}

// newProposalBlock creates a new block that proposes to issue a transaction.
// The parent of this block has ID [parentID]. The parent must be a decision block.
// Returns nil if there's an error while creating this block
func (vm *VM) newProposalBlock(parentID ids.ID, height uint64, tx Tx) (*ProposalBlock, error) {
	pb := &ProposalBlock{
		CommonBlock: CommonBlock{
			Block: core.NewBlock(parentID, height),
			vm:    vm,
		},
		Tx: tx,
	}

	// We marshal the block in this way (as a Block) so that we can unmarshal
	// it into a Block (rather than a *ProposalBlock)
	block := Block(pb)
	bytes, err := Codec.Marshal(&block)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal block: %w", err)
	}
	pb.Initialize(bytes, vm.SnowmanVM)
	return pb, nil
}
<commit_msg>reduced log level of call which can error during normal execution<commit_after>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package platformvm

import (
	"fmt"

	"github.com/ava-labs/avalanchego/database"
	"github.com/ava-labs/avalanchego/database/versiondb"
	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/snow/choices"
	"github.com/ava-labs/avalanchego/snow/consensus/snowman"
	"github.com/ava-labs/avalanchego/vms/components/core"
)

// ProposalBlock is a proposal to change the chain's state.
// A proposal may be to:
// 	1. Advance the chain's timestamp (*AdvanceTimeTx)
//  2. Remove a staker from the staker set (*RewardStakerTx)
//  3. Add a new staker to the set of pending (future) stakers (*AddStakerTx)
// The proposal will be enacted (change the chain's state) if the proposal block
// is accepted and followed by an accepted Commit block
type ProposalBlock struct {
	CommonBlock `serialize:"true"`

	Tx Tx `serialize:"true" json:"tx"`

	// The database that the chain will have if this block's proposal is committed
	onCommitDB *versiondb.Database
	// The database that the chain will have if this block's proposal is aborted
	onAbortDB *versiondb.Database
	// The function to execute if this block's proposal is committed
	onCommitFunc func() error
	// The function to execute if this block's proposal is aborted
	onAbortFunc func() error
}

// Accept implements the snowman.Block interface
func (pb *ProposalBlock) Accept() error {
	pb.SetStatus(choices.Accepted)
	pb.VM.LastAcceptedID = pb.ID()
	return nil
}

// Reject implements the snowman.Block interface
func (pb *ProposalBlock) Reject() error {
	if err := pb.vm.mempool.IssueTx(&pb.Tx); err != nil {
		pb.vm.Ctx.Log.Verbo("failed to reissue tx %q due to: %s", pb.Tx.ID(), err)
	}
	return pb.CommonBlock.Reject()
}

// Initialize this block.
// Sets [pb.vm] to [vm] and populates non-serialized fields
// This method should be called when a block is unmarshaled from bytes
func (pb *ProposalBlock) initialize(vm *VM, bytes []byte) error {
	pb.vm = vm
	pb.Block.Initialize(bytes, vm.SnowmanVM)

	unsignedBytes, err := pb.vm.codec.Marshal(&pb.Tx.UnsignedTx)
	if err != nil {
		return fmt.Errorf("failed to marshal unsigned tx: %w", err)
	}
	signedBytes, err := pb.vm.codec.Marshal(&pb.Tx)
	if err != nil {
		return fmt.Errorf("failed to marshal tx: %w", err)
	}
	pb.Tx.Initialize(unsignedBytes, signedBytes)
	return nil
}

// setBaseDatabase sets this block's base database to [db]
func (pb *ProposalBlock) setBaseDatabase(db database.Database) {
	if err := pb.onCommitDB.SetDatabase(db); err != nil {
		pb.vm.Ctx.Log.Error("problem while setting base database: %s", err)
	}
	if err := pb.onAbortDB.SetDatabase(db); err != nil {
		pb.vm.Ctx.Log.Error("problem while setting base database: %s", err)
	}
}

// onCommit should only be called after Verify is called.
// onCommit returns:
//   1. A database that contains the state of the chain assuming this proposal
//      is enacted. (That is, if this block is accepted and followed by an
//      accepted Commit block.)
//   2. A function be be executed when this block's proposal is committed.
//      This function should not write to state.
func (pb *ProposalBlock) onCommit() (*versiondb.Database, func() error) {
	return pb.onCommitDB, pb.onCommitFunc
}

// onAbort should only be called after Verify is called.
// onAbort returns a database that contains the state of the chain assuming this
// block's proposal is rejected. (That is, if this block is accepted and
// followed by an accepted Abort block.)
func (pb *ProposalBlock) onAbort() (*versiondb.Database, func() error) {
	return pb.onAbortDB, pb.onAbortFunc
}

// Verify this block is valid.
//
// The parent block must either be a Commit or an Abort block.
//
// If this block is valid, this function also sets pas.onCommit and pas.onAbort.
func (pb *ProposalBlock) Verify() error {
	tx, ok := pb.Tx.UnsignedTx.(UnsignedProposalTx)
	if !ok {
		return errWrongTxType
	}

	parentIntf := pb.parentBlock()

	// The parent of a proposal block (ie this block) must be a decision block
	parent, ok := parentIntf.(decision)
	if !ok {
		if err := pb.Reject(); err == nil {
			if err := pb.vm.DB.Commit(); err != nil {
				return fmt.Errorf("couldn't commit VM's database: %w", err)
			}
		} else {
			pb.vm.DB.Abort()
		}
		return errInvalidBlockType
	}

	// pdb is the database if this block's parent is accepted
	pdb := parent.onAccept()

	txID := tx.ID()

	var err TxError
	pb.onCommitDB, pb.onAbortDB, pb.onCommitFunc, pb.onAbortFunc, err = tx.SemanticVerify(pb.vm, pdb, &pb.Tx)
	if err != nil {
		pb.vm.droppedTxCache.Put(txID, nil) // cache tx as dropped
		// If this block's transaction proposes to advance the timestamp, the transaction may fail
		// verification now but be valid in the future, so don't (permanently) mark the block as rejected.
		if !err.Temporary() {
			if err := pb.Reject(); err == nil {
				if err := pb.vm.DB.Commit(); err != nil {
					return fmt.Errorf("couldn't commit VM's database: %w", err)
				}
			} else {
				pb.vm.DB.Abort()
			}
		}
		return err
	}

	txBytes := tx.Bytes()
	if err := pb.vm.putTx(pb.onCommitDB, txID, txBytes); err != nil {
		return fmt.Errorf("failed to put tx %s in database: %w", txID, err)
	}
	if err := pb.vm.putStatus(pb.onCommitDB, txID, Committed); err != nil {
		return fmt.Errorf("failed to put status of tx %s: %w", txID, err)
	}

	if err := pb.vm.putTx(pb.onAbortDB, txID, txBytes); err != nil {
		return fmt.Errorf("failed to put tx %s in database: %w", txID, err)
	}
	if err := pb.vm.putStatus(pb.onAbortDB, txID, Aborted); err != nil {
		return fmt.Errorf("failed to put status of tx %s: %w", txID, err)
	}

	pb.vm.currentBlocks[pb.ID().Key()] = pb
	parentIntf.addChild(pb)
	return nil
}

// Options returns the possible children of this block in preferential order.
func (pb *ProposalBlock) Options() ([2]snowman.Block, error) {
	blockID := pb.ID()

	commit, err := pb.vm.newCommitBlock(blockID, pb.Height()+1)
	if err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to create commit block: %w", err)
	}
	abort, err := pb.vm.newAbortBlock(blockID, pb.Height()+1)
	if err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to create abort block: %w", err)
	}

	if err := pb.vm.State.PutBlock(pb.vm.DB, commit); err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to put commit block: %w", err)
	}
	if err := pb.vm.State.PutBlock(pb.vm.DB, abort); err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to put abort block: %w", err)
	}
	if err := pb.vm.DB.Commit(); err != nil {
		return [2]snowman.Block{}, fmt.Errorf("failed to commit VM's database: %w", err)
	}

	tx, ok := pb.Tx.UnsignedTx.(UnsignedProposalTx)
	if !ok {
		return [2]snowman.Block{}, errWrongTxType
	}

	if tx.InitiallyPrefersCommit(pb.vm) {
		return [2]snowman.Block{commit, abort}, nil
	}
	return [2]snowman.Block{abort, commit}, nil
}

// newProposalBlock creates a new block that proposes to issue a transaction.
// The parent of this block has ID [parentID]. The parent must be a decision block.
// Returns nil if there's an error while creating this block
func (vm *VM) newProposalBlock(parentID ids.ID, height uint64, tx Tx) (*ProposalBlock, error) {
	pb := &ProposalBlock{
		CommonBlock: CommonBlock{
			Block: core.NewBlock(parentID, height),
			vm:    vm,
		},
		Tx: tx,
	}

	// We marshal the block in this way (as a Block) so that we can unmarshal
	// it into a Block (rather than a *ProposalBlock)
	block := Block(pb)
	bytes, err := Codec.Marshal(&block)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal block: %w", err)
	}
	pb.Initialize(bytes, vm.SnowmanVM)
	return pb, nil
}
<|endoftext|>


<commit_before>// Абстрактная фабрика (Abstract Factory)
//

package abstract_factory

// Тип AbstractFactory, описывает интерфейс Абстрактной Фабрики
// по производству газировки.
// Конкретные фабрики должны его реализовать.
type AbstractFactory interface {
	createWater(volume float64) AbstractWater
	createBottle(volume float64) AbstractBottle
}

// Тип AbstractWater, описывает интерфейс напитка.
type AbstractWater interface {
	GetVolume() float64 // Возможность получения объема
}

// Тип AbstractBottle, описывает интерфейс бутылки.
type AbstractBottle interface {
	InteractWater(water AbstractWater)  // Бутылка взаидодействует с напитком
	GetBottleVolume() float64           // Возможность получения объема бутылки
	GetWaterVolume() float64            // Возможность получения объема напитка в бутылке
}

// Тип CocaColaFactory, реализует фабрику по производству CocaCola.
type CocaColaFactory struct {
}

// Создает воду
func (self *CocaColaFactory) createWater(volume float64) *CocaColaWater {
	return &CocaColaWater{volume: volume}
}

// Создает бутылку
func (self *CocaColaFactory) createBottle(volume float64) *CocaColaBottle {
	return &CocaColaBottle{volume: volume}
}

// Напиток CocaCola
type CocaColaWater struct {
	volume float64 // Объем созданого напитка
}

// Получить объем созданого напитка
func (self *CocaColaWater) GetVolume() float64 {
	return self.volume
}

// Бутылка CocaCola
type CocaColaBottle struct {
	water  *CocaColaWater // Бутылка должна содержать напиток
	volume float64        // Объем бутылки
}

// Наливаем напиток в бутылку
func (self *CocaColaBottle) InteractWater(water *CocaColaWater) {
	self.water = water
}

// Получить объем бутылки
func (self *CocaColaBottle) GetBottleVolume() float64 {
	return self.volume
}

// Получить объем налитого напитка
func (self *CocaColaBottle) GetWaterVolume() float64 {
	return self.water.GetVolume()
}
<commit_msg>fixed syntax error<commit_after>// Абстрактная фабрика (Abstract Factory)
//

package abstract_factory

// Тип AbstractFactory, описывает интерфейс Абстрактной Фабрики
// по производству газировки.
// Конкретные фабрики должны его реализовать.
type AbstractFactory interface {
	createWater(volume float64) AbstractWater
	createBottle(volume float64) AbstractBottle
}

// Тип AbstractWater, описывает интерфейс напитка.
type AbstractWater interface {
	GetVolume() float64 // Возможность получения объема
}

// Тип AbstractBottle, описывает интерфейс бутылки.
type AbstractBottle interface {
	InteractWater(water AbstractWater)  // Бутылка взаимодействует с напитком
	GetBottleVolume() float64           // Возможность получения объема бутылки
	GetWaterVolume() float64            // Возможность получения объема напитка в бутылке
}

// Тип CocaColaFactory, реализует фабрику по производству CocaCola.
type CocaColaFactory struct {
}

// Создает воду
func (self *CocaColaFactory) createWater(volume float64) *CocaColaWater {
	return &CocaColaWater{volume: volume}
}

// Создает бутылку
func (self *CocaColaFactory) createBottle(volume float64) *CocaColaBottle {
	return &CocaColaBottle{volume: volume}
}

// Напиток CocaCola
type CocaColaWater struct {
	volume float64 // Объем созданого напитка
}

// Получить объем созданого напитка
func (self *CocaColaWater) GetVolume() float64 {
	return self.volume
}

// Бутылка CocaCola
type CocaColaBottle struct {
	water  *CocaColaWater // Бутылка должна содержать напиток
	volume float64        // Объем бутылки
}

// Наливаем напиток в бутылку
func (self *CocaColaBottle) InteractWater(water *CocaColaWater) {
	self.water = water
}

// Получить объем бутылки
func (self *CocaColaBottle) GetBottleVolume() float64 {
	return self.volume
}

// Получить объем налитого напитка
func (self *CocaColaBottle) GetWaterVolume() float64 {
	return self.water.GetVolume()
}
<|endoftext|>


<commit_before>package abm

import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"math/rand"

	"github.com/benjamin-rood/abm-colour-polymorphism/calc"
	"github.com/benjamin-rood/abm-colour-polymorphism/colour"
	"github.com/benjamin-rood/abm-colour-polymorphism/geometry"
	"github.com/benjamin-rood/abm-colour-polymorphism/render"
)

// ColourPolymorphicPrey – Prey agent type for Predator-Prey ABM
type ColourPolymorphicPrey struct {
	pos         geometry.Vector //	position in the environment
	movS        float64         //	speed
	movA        float64         //	acceleration
	dir𝚯        float64         //	 heading angle
	dir         geometry.Vector //	must be implemented as a unit vector
	Rτ          float64         // turn rate / range (in radians)
	sr          float64         //	search range
	lifespan    int
	hunger      int        //	counter for interval between needing food
	fertility   int        //	counter for interval between birth and sex
	gravid      bool       //	i.e. pregnant
	colouration colour.RGB //	colour
	𝛘           float64    //	 colour sorting value - colour distance/difference between vp.imprimt and cpp.colouration
	ϸ           float64    //  position sorting value - vector distance between vp.pos and cpp.pos
}

// String returns a clear textual presentation the internal values of the CPP agent
func (c *ColourPolymorphicPrey) String() string {
	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("pos=(%v,%v)\n", c.pos[x], c.pos[y]))
	buffer.WriteString(fmt.Sprintf("movS=%v\n", c.movS))
	buffer.WriteString(fmt.Sprintf("movA=%v\n", c.movA))
	buffer.WriteString(fmt.Sprintf("dir𝚯=%v\n", c.dir𝚯))
	buffer.WriteString(fmt.Sprintf("dir=(%v,%v)\n", c.dir[x], c.dir[y]))
	buffer.WriteString(fmt.Sprintf("Rτ=%v\n", c.Rτ))
	buffer.WriteString(fmt.Sprintf("sr=%v\n", c.sr))
	buffer.WriteString(fmt.Sprintf("lifespan=%v\n", c.lifespan))
	buffer.WriteString(fmt.Sprintf("hunger=%v\n", c.hunger))
	buffer.WriteString(fmt.Sprintf("fertility=%v\n", c.fertility))
	buffer.WriteString(fmt.Sprintf("gravid=%v\n", c.gravid))
	buffer.WriteString(fmt.Sprintf("colouration=%v\n", c.colouration))
	return buffer.String()
}

// GetDrawInfo exports the data set needed for agent visualisation.
func (c *ColourPolymorphicPrey) GetDrawInfo() (ar render.AgentRender) {
	ar.Type = "cpp"
	ar.X = c.pos[x]
	ar.Y = c.pos[y]
	ar.Colour = c.colouration.To256()
	return
}

// GeneratePopulationCPP will create `size` number of Colour Polymorphic Prey agents
func GeneratePopulationCPP(size int, context Context) (pop []ColourPolymorphicPrey) {
	for i := 0; i < size; i++ {
		agent := ColourPolymorphicPrey{}
		agent.pos = geometry.RandVector(context.Bounds)
		if context.CppAgeing {
			if context.RandomAges {
				agent.lifespan = calc.RandIntIn(int(float64(context.CppLifespan)*0.7), int(float64(context.CppLifespan)*1.3))
			} else {
				agent.lifespan = context.CppLifespan
			}
		} else {
			agent.lifespan = 99999
		}
		agent.movS = context.CppS
		agent.movA = context.CppA
		agent.dir𝚯 = rand.Float64() * (2 * math.Pi)
		agent.dir = geometry.UnitVector(agent.dir𝚯)
		agent.Rτ = context.CppTurn
		agent.sr = context.CppSr
		agent.hunger = 0
		agent.fertility = 1
		agent.gravid = false
		agent.colouration = colour.RandRGB()
		agent.𝛘 = 0.0
		agent.ϸ = 0.0
		pop = append(pop, agent)
	}
	return
}

func spawn(size int, parent ColourPolymorphicPrey, context Context) (pop []ColourPolymorphicPrey) {
	for i := 0; i < size; i++ {
		agent := parent
		agent.pos = parent.pos
		if context.CppAgeing {
			if context.RandomAges {
				agent.lifespan = calc.RandIntIn(int(float64(context.CppLifespan)*0.7), int(float64(context.CppLifespan)*1.3))
			} else {
				agent.lifespan = context.CppLifespan
			}
		} else {
			agent.lifespan = 99999 //	i.e. Undead!
		}
		agent.movS = parent.movS
		agent.movA = parent.movA
		agent.dir𝚯 = rand.Float64() * (2 * math.Pi)
		agent.dir = geometry.UnitVector(agent.dir𝚯)
		agent.Rτ = parent.Rτ
		agent.sr = parent.sr
		agent.hunger = 0
		agent.fertility = 1
		agent.gravid = false
		agent.colouration = parent.colouration
		agent.𝛘 = 0.0
		agent.ϸ = 0.0
		pop = append(pop, agent)
	}
	return
}

// ProximitySort implements sort.Interface for []ColourPolymorphicPrey
// based on δ field.
type ProximitySort []ColourPolymorphicPrey

func (ps ProximitySort) Len() int           { return len(ps) }
func (ps ProximitySort) Swap(i, j int)      { ps[i], ps[j] = ps[j], ps[i] }
func (ps ProximitySort) Less(i, j int) bool { return ps[i].ϸ < ps[j].ϸ }

// VisualSort implements sort.Interface for []ColourPolymorphicPrey
// based on 𝛘 field – to assert visual bias of a VisualPredator based on it's colour imprinting value.
type VisualSort []ColourPolymorphicPrey

func (vs VisualSort) Len() int           { return len(vs) }
func (vs VisualSort) Swap(i, j int)      { vs[i], vs[j] = vs[j], vs[i] }
func (vs VisualSort) Less(i, j int) bool { return vs[i].𝛘 < vs[j].𝛘 }

/*
The Colour Polymorphic Prey agent is currently defined by the following animalistic interfaces:
Mover
Breeder
Mortal
*/

// Mover interface:

// Turn implements agent Mover interface method for ColourPolymorphicPrey:
// updates dir𝚯 and dir vector to the new heading offset by 𝚯
func (c *ColourPolymorphicPrey) Turn(𝚯 float64) {
	newHeading := geometry.UnitAngle(c.dir𝚯 + 𝚯)
	c.dir = geometry.UnitVector(newHeading)
	c.dir𝚯 = newHeading
}

// Move implements agent Mover interface method for ColourPolymorphicPrey:
// updates the agent's position according to its direction (heading) and
// velocity (speed*acceleration) if it doesn't encounter any errors.
func (c *ColourPolymorphicPrey) Move() error {
	var posOffset, newPos geometry.Vector
	var err error
	posOffset, err = geometry.VecScalarMultiply(c.dir, c.movS*c.movA)
	if err != nil {
		return errors.New("agent move failed: " + err.Error())
	}
	newPos, err = geometry.VecAddition(c.pos, posOffset)
	if err != nil {
		return errors.New("agent move failed: " + err.Error())
	}
	newPos[x] = calc.WrapFloatIn(newPos[x], -1.0, 1.0)
	newPos[y] = calc.WrapFloatIn(newPos[y], -1.0, 1.0)
	c.pos = newPos
	return nil
}

// Breeder interface:

// MateSearch implements Breeder interface method for ColourPolymorphicPrey:
// NEEDS BETTER HANDLING THAN JUST PUSHING THE ERROR UP!
func (c *ColourPolymorphicPrey) MateSearch(pop []ColourPolymorphicPrey, skip int) (mate *ColourPolymorphicPrey, err error) {
	mate = nil
	err = nil
	dist := 0.0
	for i := 0; i < len(pop); i++ {
		if i == skip {
			continue
		}
		dist, err = geometry.VectorDistance(c.pos, pop[i].pos)
		if err != nil {
			return
		}
		if dist <= c.sr {
			mate = &pop[i]
			return
		}
	}
	return
}

// Reproduction implements Breeder interface method - ASEXUAL (self-reproduction) ColourPolymorphicPrey:
func (c *ColourPolymorphicPrey) Reproduction(chance float64, gestation int) bool {
	c.hunger++ //	energy cost
	ω := rand.Float64()
	if ω <= chance {
		c.gravid = true
		c.fertility = -gestation
		return true
	}
	c.fertility = 1
	return false
}

// Copulation implemets Breeder interface method for ColourPolymorphicPrey:
func (c *ColourPolymorphicPrey) copulation(mate *ColourPolymorphicPrey, chance float64, gestation int, sexualCost int) bool {
	if mate == nil {
		return false
	}
	if mate.fertility < sexualCost { //	mate must be sufficiently fertile also
		return false
	}
	ω := rand.Float64()
	mate.fertility = -sexualCost // it takes two to tango, buddy!
	if ω <= chance {
		c.gravid = true
		c.fertility = -gestation
		return true
	}
	c.fertility = 1
	return false
}

// Birth implemets Breeder interface method for ColourPolymorphicPrey:
func (c *ColourPolymorphicPrey) Birth(ctxt Context) []ColourPolymorphicPrey {
	n := 1
	if ctxt.CppSpawnSize > 1 {
		n = rand.Intn(ctxt.CppSpawnSize) + 1 //	i.e. range [1, b]
	}
	progeny := spawn(n, *c, ctxt)
	for i := 0; i < len(progeny); i++ {
		progeny[i].mutation(ctxt.MutationFactor)
		progeny[i].pos, _ = geometry.FuzzifyVector(c.pos, c.movS)
	}
	c.hunger++ //	energy cost
	c.gravid = false
	return progeny
}

// For now, mutation only affects colouration, but could be extended to affect any other parameter.
func (c *ColourPolymorphicPrey) mutation(Mf float64) {
	c.colouration = colour.RandRGBClamped(c.colouration, Mf)
}

// Mutation for external testing only
func Mutation(c *ColourPolymorphicPrey, Mf float64) {
	c.colouration = colour.RandRGBClamped(c.colouration, Mf)
}

// set of methods implementing Mortal interface

// Age decrements the lifespan of an agent,
// and applies the effects of ageing (if any)
func (c *ColourPolymorphicPrey) Age(ctxt Context) (jump string) {
	c.hunger++
	c.fertility++
	if ctxt.CppAgeing {
		c.lifespan--
	}
	switch {
	case c.lifespan <= 0:
		jump = "DEATH"
	case c.fertility == 0:
		c.gravid = false
		jump = "SPAWN"
	case c.fertility >= ctxt.CppSexualCost: // period / sexual cost
		jump = "FERTILE"
	default:
		jump = "EXPLORE"
	}
	return
}
<commit_msg>changed cp-prey distance sorting field name to δ (delta) to correctly match comments and documentation.<commit_after>package abm

import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"math/rand"

	"github.com/benjamin-rood/abm-colour-polymorphism/calc"
	"github.com/benjamin-rood/abm-colour-polymorphism/colour"
	"github.com/benjamin-rood/abm-colour-polymorphism/geometry"
	"github.com/benjamin-rood/abm-colour-polymorphism/render"
)

// ColourPolymorphicPrey – Prey agent type for Predator-Prey ABM
type ColourPolymorphicPrey struct {
	pos         geometry.Vector //	position in the environment
	movS        float64         //	speed
	movA        float64         //	acceleration
	dir𝚯        float64         //	 heading angle
	dir         geometry.Vector //	must be implemented as a unit vector
	Rτ          float64         // turn rate / range (in radians)
	sr          float64         //	search range
	lifespan    int
	hunger      int        //	counter for interval between needing food
	fertility   int        //	counter for interval between birth and sex
	gravid      bool       //	i.e. pregnant
	colouration colour.RGB //	colour
	𝛘           float64    //	 colour sorting value - colour distance/difference between vp.imprimt and cpp.colouration
	δ           float64    //  position sorting value - vector distance between vp.pos and cpp.pos
}

// String returns a clear textual presentation the internal values of the CPP agent
func (c *ColourPolymorphicPrey) String() string {
	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("pos=(%v,%v)\n", c.pos[x], c.pos[y]))
	buffer.WriteString(fmt.Sprintf("movS=%v\n", c.movS))
	buffer.WriteString(fmt.Sprintf("movA=%v\n", c.movA))
	buffer.WriteString(fmt.Sprintf("dir𝚯=%v\n", c.dir𝚯))
	buffer.WriteString(fmt.Sprintf("dir=(%v,%v)\n", c.dir[x], c.dir[y]))
	buffer.WriteString(fmt.Sprintf("Rτ=%v\n", c.Rτ))
	buffer.WriteString(fmt.Sprintf("sr=%v\n", c.sr))
	buffer.WriteString(fmt.Sprintf("lifespan=%v\n", c.lifespan))
	buffer.WriteString(fmt.Sprintf("hunger=%v\n", c.hunger))
	buffer.WriteString(fmt.Sprintf("fertility=%v\n", c.fertility))
	buffer.WriteString(fmt.Sprintf("gravid=%v\n", c.gravid))
	buffer.WriteString(fmt.Sprintf("colouration=%v\n", c.colouration))
	return buffer.String()
}

// GetDrawInfo exports the data set needed for agent visualisation.
func (c *ColourPolymorphicPrey) GetDrawInfo() (ar render.AgentRender) {
	ar.Type = "cpp"
	ar.X = c.pos[x]
	ar.Y = c.pos[y]
	ar.Colour = c.colouration.To256()
	return
}

// GeneratePopulationCPP will create `size` number of Colour Polymorphic Prey agents
func GeneratePopulationCPP(size int, context Context) (pop []ColourPolymorphicPrey) {
	for i := 0; i < size; i++ {
		agent := ColourPolymorphicPrey{}
		agent.pos = geometry.RandVector(context.Bounds)
		if context.CppAgeing {
			if context.RandomAges {
				agent.lifespan = calc.RandIntIn(int(float64(context.CppLifespan)*0.7), int(float64(context.CppLifespan)*1.3))
			} else {
				agent.lifespan = context.CppLifespan
			}
		} else {
			agent.lifespan = 99999
		}
		agent.movS = context.CppS
		agent.movA = context.CppA
		agent.dir𝚯 = rand.Float64() * (2 * math.Pi)
		agent.dir = geometry.UnitVector(agent.dir𝚯)
		agent.Rτ = context.CppTurn
		agent.sr = context.CppSr
		agent.hunger = 0
		agent.fertility = 1
		agent.gravid = false
		agent.colouration = colour.RandRGB()
		agent.𝛘 = 0.0
		agent.δ = 0.0
		pop = append(pop, agent)
	}
	return
}

func spawn(size int, parent ColourPolymorphicPrey, context Context) (pop []ColourPolymorphicPrey) {
	for i := 0; i < size; i++ {
		agent := parent
		agent.pos = parent.pos
		if context.CppAgeing {
			if context.RandomAges {
				agent.lifespan = calc.RandIntIn(int(float64(context.CppLifespan)*0.7), int(float64(context.CppLifespan)*1.3))
			} else {
				agent.lifespan = context.CppLifespan
			}
		} else {
			agent.lifespan = 99999 //	i.e. Undead!
		}
		agent.movS = parent.movS
		agent.movA = parent.movA
		agent.dir𝚯 = rand.Float64() * (2 * math.Pi)
		agent.dir = geometry.UnitVector(agent.dir𝚯)
		agent.Rτ = parent.Rτ
		agent.sr = parent.sr
		agent.hunger = 0
		agent.fertility = 1
		agent.gravid = false
		agent.colouration = parent.colouration
		agent.𝛘 = 0.0
		agent.δ = 0.0
		pop = append(pop, agent)
	}
	return
}

// ProximitySort implements sort.Interface for []ColourPolymorphicPrey
// based on δ field.
type ProximitySort []ColourPolymorphicPrey

func (ps ProximitySort) Len() int           { return len(ps) }
func (ps ProximitySort) Swap(i, j int)      { ps[i], ps[j] = ps[j], ps[i] }
func (ps ProximitySort) Less(i, j int) bool { return ps[i].δ < ps[j].δ }

// VisualSort implements sort.Interface for []ColourPolymorphicPrey
// based on 𝛘 field – to assert visual bias of a VisualPredator based on it's colour imprinting value.
type VisualSort []ColourPolymorphicPrey

func (vs VisualSort) Len() int           { return len(vs) }
func (vs VisualSort) Swap(i, j int)      { vs[i], vs[j] = vs[j], vs[i] }
func (vs VisualSort) Less(i, j int) bool { return vs[i].𝛘 < vs[j].𝛘 }

/*
The Colour Polymorphic Prey agent is currently defined by the following animalistic interfaces:
Mover
Breeder
Mortal
*/

// Mover interface:

// Turn implements agent Mover interface method for ColourPolymorphicPrey:
// updates dir𝚯 and dir vector to the new heading offset by 𝚯
func (c *ColourPolymorphicPrey) Turn(𝚯 float64) {
	newHeading := geometry.UnitAngle(c.dir𝚯 + 𝚯)
	c.dir = geometry.UnitVector(newHeading)
	c.dir𝚯 = newHeading
}

// Move implements agent Mover interface method for ColourPolymorphicPrey:
// updates the agent's position according to its direction (heading) and
// velocity (speed*acceleration) if it doesn't encounter any errors.
func (c *ColourPolymorphicPrey) Move() error {
	var posOffset, newPos geometry.Vector
	var err error
	posOffset, err = geometry.VecScalarMultiply(c.dir, c.movS*c.movA)
	if err != nil {
		return errors.New("agent move failed: " + err.Error())
	}
	newPos, err = geometry.VecAddition(c.pos, posOffset)
	if err != nil {
		return errors.New("agent move failed: " + err.Error())
	}
	newPos[x] = calc.WrapFloatIn(newPos[x], -1.0, 1.0)
	newPos[y] = calc.WrapFloatIn(newPos[y], -1.0, 1.0)
	c.pos = newPos
	return nil
}

// Breeder interface:

// MateSearch implements Breeder interface method for ColourPolymorphicPrey:
// NEEDS BETTER HANDLING THAN JUST PUSHING THE ERROR UP!
func (c *ColourPolymorphicPrey) MateSearch(pop []ColourPolymorphicPrey, skip int) (mate *ColourPolymorphicPrey, err error) {
	mate = nil
	err = nil
	dist := 0.0
	for i := 0; i < len(pop); i++ {
		if i == skip {
			continue
		}
		dist, err = geometry.VectorDistance(c.pos, pop[i].pos)
		if err != nil {
			return
		}
		if dist <= c.sr {
			mate = &pop[i]
			return
		}
	}
	return
}

// Reproduction implements Breeder interface method - ASEXUAL (self-reproduction) ColourPolymorphicPrey:
func (c *ColourPolymorphicPrey) Reproduction(chance float64, gestation int) bool {
	c.hunger++ //	energy cost
	ω := rand.Float64()
	if ω <= chance {
		c.gravid = true
		c.fertility = -gestation
		return true
	}
	c.fertility = 1
	return false
}

// Copulation implemets Breeder interface method for ColourPolymorphicPrey:
func (c *ColourPolymorphicPrey) copulation(mate *ColourPolymorphicPrey, chance float64, gestation int, sexualCost int) bool {
	if mate == nil {
		return false
	}
	if mate.fertility < sexualCost { //	mate must be sufficiently fertile also
		return false
	}
	ω := rand.Float64()
	mate.fertility = -sexualCost // it takes two to tango, buddy!
	if ω <= chance {
		c.gravid = true
		c.fertility = -gestation
		return true
	}
	c.fertility = 1
	return false
}

// Birth implemets Breeder interface method for ColourPolymorphicPrey:
func (c *ColourPolymorphicPrey) Birth(ctxt Context) []ColourPolymorphicPrey {
	n := 1
	if ctxt.CppSpawnSize > 1 {
		n = rand.Intn(ctxt.CppSpawnSize) + 1 //	i.e. range [1, b]
	}
	progeny := spawn(n, *c, ctxt)
	for i := 0; i < len(progeny); i++ {
		progeny[i].mutation(ctxt.MutationFactor)
		progeny[i].pos, _ = geometry.FuzzifyVector(c.pos, c.movS)
	}
	c.hunger++ //	energy cost
	c.gravid = false
	return progeny
}

// For now, mutation only affects colouration, but could be extended to affect any other parameter.
func (c *ColourPolymorphicPrey) mutation(Mf float64) {
	c.colouration = colour.RandRGBClamped(c.colouration, Mf)
}

// Mutation for external testing only
func Mutation(c *ColourPolymorphicPrey, Mf float64) {
	c.colouration = colour.RandRGBClamped(c.colouration, Mf)
}

// set of methods implementing Mortal interface

// Age decrements the lifespan of an agent,
// and applies the effects of ageing (if any)
func (c *ColourPolymorphicPrey) Age(ctxt Context) (jump string) {
	c.hunger++
	c.fertility++
	if ctxt.CppAgeing {
		c.lifespan--
	}
	switch {
	case c.lifespan <= 0:
		jump = "DEATH"
	case c.fertility == 0:
		c.gravid = false
		jump = "SPAWN"
	case c.fertility >= ctxt.CppSexualCost: // period / sexual cost
		jump = "FERTILE"
	default:
		jump = "EXPLORE"
	}
	return
}
<|endoftext|>


<commit_before>package ipvs

import (
	"errors"
	"sync"

	"github.com/luizbafilho/fusis/api/types"
)

var ErrNotFound = errors.New("Not found")

type State interface {
	GetServices() []types.Service
	GetService(name string) (*types.Service, error)
	AddService(svc *types.Service)
	DeleteService(svc *types.Service)

	GetDestination(name string) (*types.Destination, error)
	AddDestination(dst *types.Destination)
	DeleteDestination(dst *types.Destination)
}

type FusisState struct {
	sync.Mutex
	Services     map[string]types.Service
	Destinations map[string]types.Destination
}

func NewFusisState() *FusisState {
	return &FusisState{
		Services:     make(map[string]types.Service),
		Destinations: make(map[string]types.Destination),
	}
}

func (s *FusisState) GetServices() []types.Service {
	services := []types.Service{}
	for _, v := range s.Services {
		s.getDestinations(&v)
		services = append(services, v)
	}

	return services
}

func (s *FusisState) GetService(name string) (*types.Service, error) {
	svc := s.Services[name]

	if svc.Name == "" {
		return nil, ErrNotFound
	}

	s.getDestinations(&svc)

	return &svc, nil
}

func (s *FusisState) getDestinations(svc *types.Service) {
	dsts := []types.Destination{}

	for _, d := range s.Destinations {
		if d.ServiceId == svc.GetId() {
			dsts = append(dsts, d)
		}
	}

	svc.Destinations = dsts
}

func (s *FusisState) AddService(svc *types.Service) {
	s.Lock()
	defer s.Unlock()

	s.Services[svc.GetId()] = *svc
}

func (s *FusisState) DeleteService(svc *types.Service) {
	s.Lock()
	defer s.Unlock()

	delete(s.Services, svc.GetId())
}

func (s *FusisState) GetDestination(name string) (*types.Destination, error) {
	dst := s.Destinations[name]

	if dst.Name == "" {
		return nil, ErrNotFound
	}

	return &dst, nil
}

func (s *FusisState) AddDestination(dst *types.Destination) {
	s.Lock()
	defer s.Unlock()

	s.Destinations[dst.GetId()] = *dst
}

func (s *FusisState) DeleteDestination(dst *types.Destination) {
	s.Lock()
	defer s.Unlock()

	delete(s.Destinations, dst.GetId())
}
<commit_msg>Function to collect stats from Services and Destinations<commit_after>package ipvs

import (
	"errors"
	"sync"
	"time"
    "strings"

	"github.com/Sirupsen/logrus"

	"github.com/luizbafilho/fusis/api/types"
)

var ErrNotFound = errors.New("Not found")

type State interface {
	GetServices() []types.Service
	GetService(name string) (*types.Service, error)
	AddService(svc *types.Service)
	DeleteService(svc *types.Service)

	GetDestination(name string) (*types.Destination, error)
	AddDestination(dst *types.Destination)
	DeleteDestination(dst *types.Destination)
    CollectStats(tick time.Time)
}

type FusisState struct {
	sync.Mutex
	Services     map[string]types.Service
	Destinations map[string]types.Destination
}

func NewFusisState() *FusisState {
	return &FusisState{
		Services:     make(map[string]types.Service),
		Destinations: make(map[string]types.Destination),
	}
}

func (s *FusisState) GetServices() []types.Service {
	services := []types.Service{}
	for _, v := range s.Services {
		s.getDestinations(&v)
		services = append(services, v)
	}

	return services
}

func (s *FusisState) GetService(name string) (*types.Service, error) {
	svc := s.Services[name]

	if svc.Name == "" {
		return nil, ErrNotFound
	}

	s.getDestinations(&svc)

	return &svc, nil
}

func (s *FusisState) getDestinations(svc *types.Service) {
	dsts := []types.Destination{}

	for _, d := range s.Destinations {
		if d.ServiceId == svc.GetId() {
			dsts = append(dsts, d)
		}
	}

	svc.Destinations = dsts
}

func (s *FusisState) AddService(svc *types.Service) {
	s.Lock()
	defer s.Unlock()

	s.Services[svc.GetId()] = *svc
}

func (s *FusisState) DeleteService(svc *types.Service) {
	s.Lock()
	defer s.Unlock()

	delete(s.Services, svc.GetId())
}

func (s *FusisState) GetDestination(name string) (*types.Destination, error) {
	dst := s.Destinations[name]

	if dst.Name == "" {
		return nil, ErrNotFound
	}

	return &dst, nil
}

func (s *FusisState) AddDestination(dst *types.Destination) {
	s.Lock()
	defer s.Unlock()

	s.Destinations[dst.GetId()] = *dst
}

func (s *FusisState) DeleteDestination(dst *types.Destination) {
	s.Lock()
	defer s.Unlock()

	delete(s.Destinations, dst.GetId())
}

func (s *FusisState) CollectStats (tick time.Time) {

    logger := logrus.New()
    for _, v := range s.GetServices() {

        hosts := []string{}
        for _, i := range v.Destinations {
            hosts = append(hosts, i.Host)
        }

        logger.WithFields(logrus.Fields{
            "time": tick,
            "service": v.Name,
            "Protocol": v.Protocol,
            "Port": v.Port,
            "hosts": strings.Join(hosts, ","),
        }).Info("Fusis router stats")
    }
}
<|endoftext|>


<commit_before>package mysql

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/jmoiron/sqlx"
	"github.com/solefaucet/sole-server/errors"
	"github.com/solefaucet/sole-server/models"
)

// CreateWithdrawal creates a new withdrawal
func (s Storage) CreateWithdrawal(withdrawal models.Withdrawal) error {
	tx := s.db.MustBegin()

	// create withdrawal with transaction
	if err := createWithdrawal(tx, withdrawal); err != nil {
		tx.Rollback()
		return err
	}

	// commit transaction
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("create withdrawal commit transaction error: %v", err)
	}

	return nil
}

func createWithdrawal(tx *sqlx.Tx, withdrawal models.Withdrawal) error {
	if err := deductUserBalanceBy(tx, withdrawal.UserID, withdrawal.Amount); err != nil {
		return err
	}
	return insertWithdrawal(tx, withdrawal.UserID, withdrawal.Address, withdrawal.Amount)
}

func deductUserBalanceBy(tx *sqlx.Tx, userID int64, delta float64) error {
	result, err := tx.Exec("UPDATE users SET `balance` = `balance` - ? WHERE `id` = ? AND `balance` >= ?", delta, userID, delta)
	if err != nil {
		return fmt.Errorf("deduct user balance error: %v", err)
	}

	// make sure the user has sufficient balance
	if rowAffected, _ := result.RowsAffected(); rowAffected != 1 {
		return errors.ErrInsufficientBalance
	}

	return nil
}

func insertWithdrawal(tx *sqlx.Tx, userID int64, address string, amount float64) error {
	rawSQL := "INSERT INTO withdrawals (`user_id`, `address`, `amount`) VALUES (?, ?, ?)"
	if _, err := tx.Exec(rawSQL, userID, address, amount); err != nil {
		return fmt.Errorf("create withdrawal error: %v", err)
	}

	return nil
}

// GetWithdrawals get user's withdrawal
func (s Storage) GetWithdrawals(userID int64, limit, offset int64) ([]models.Withdrawal, error) {
	rawSQL := "SELECT * FROM `withdrawals` WHERE `user_id` = ? ORDER BY `id` DESC LIMIT ? OFFSET ?"
	args := []interface{}{userID, limit, offset}
	dest := []models.Withdrawal{}
	err := s.selects(&dest, rawSQL, args...)
	return dest, err
}

// GetNumberOfWithdrawals gets number of user's withdrawals
func (s Storage) GetNumberOfWithdrawals(userID int64) (int64, error) {
	var count int64
	err := s.db.QueryRowx("SELECT COUNT(*) FROM `withdrawals` WHERE `user_id` = ?", userID).Scan(&count)
	return count, err
}

// GetPendingWithdrawals get all unprocessed withdrawals
func (s Storage) GetPendingWithdrawals() ([]models.Withdrawal, error) {
	rawSQL := "SELECT * FROM withdrawals WHERE `status` = ? ORDER BY `id` asc"
	args := []interface{}{models.WithdrawalStatusPending}
	dest := []models.Withdrawal{}
	err := s.selects(&dest, rawSQL, args...)
	return dest, err
}

// UpdateWithdrawalStatusToProcessing update withdrawal status to processing if status = pending
func (s Storage) UpdateWithdrawalStatusToProcessing(ids []int64) error {
	rawSQL, args, err := sqlx.In(
		"UPDATE `withdrawals` SET `status` = ? WHERE `id` IN (?) AND `status` = ?",
		models.WithdrawalStatusProcessing,
		ids,
		models.WithdrawalStatusPending,
	)
	if err != nil {
		return fmt.Errorf("update withdrawal status to processing build sql with in: %v", err)
	}

	logrus.WithFields(logrus.Fields{
		"sql":  rawSQL,
		"args": args,
	}).Info("sql update withdrawals status to processing")

	result, err := s.db.Exec(rawSQL, args...)
	if err != nil {
		return err
	}

	if rowAffected, _ := result.RowsAffected(); rowAffected != 1 {
		return fmt.Errorf("expected 1 but %v rows affected", rowAffected)
	}

	return nil
}

// UpdateWithdrawalStatusToProcessed update withdrawal status to processed if status = processing
func (s Storage) UpdateWithdrawalStatusToProcessed(ids []int64, transactionID string) error {
	rawSQL, args, err := sqlx.In(
		"UPDATE `withdrawals` SET `status` = ?, `transaction_id` = ? WHERE `id` IN (?) AND `status` = ?",
		models.WithdrawalStatusProcessed,
		transactionID,
		ids,
		models.WithdrawalStatusProcessing,
	)
	if err != nil {
		return fmt.Errorf("update withdrawal status to processed build sql with in: %v", err)
	}

	logrus.WithFields(logrus.Fields{
		"sql":  rawSQL,
		"args": args,
	}).Info("sql update withdrawals status to processed")

	result, err := s.db.Exec(rawSQL, args...)
	if err != nil {
		return err
	}

	if rowAffected, _ := result.RowsAffected(); rowAffected != 1 {
		return fmt.Errorf("expected 1 but %v rows affected", rowAffected)
	}

	return nil
}
<commit_msg>Fix bug that rows affected should be equal to number of ids<commit_after>package mysql

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/jmoiron/sqlx"
	"github.com/solefaucet/sole-server/errors"
	"github.com/solefaucet/sole-server/models"
)

// CreateWithdrawal creates a new withdrawal
func (s Storage) CreateWithdrawal(withdrawal models.Withdrawal) error {
	tx := s.db.MustBegin()

	// create withdrawal with transaction
	if err := createWithdrawal(tx, withdrawal); err != nil {
		tx.Rollback()
		return err
	}

	// commit transaction
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("create withdrawal commit transaction error: %v", err)
	}

	return nil
}

func createWithdrawal(tx *sqlx.Tx, withdrawal models.Withdrawal) error {
	if err := deductUserBalanceBy(tx, withdrawal.UserID, withdrawal.Amount); err != nil {
		return err
	}
	return insertWithdrawal(tx, withdrawal.UserID, withdrawal.Address, withdrawal.Amount)
}

func deductUserBalanceBy(tx *sqlx.Tx, userID int64, delta float64) error {
	result, err := tx.Exec("UPDATE users SET `balance` = `balance` - ? WHERE `id` = ? AND `balance` >= ?", delta, userID, delta)
	if err != nil {
		return fmt.Errorf("deduct user balance error: %v", err)
	}

	// make sure the user has sufficient balance
	if rowAffected, _ := result.RowsAffected(); rowAffected != 1 {
		return errors.ErrInsufficientBalance
	}

	return nil
}

func insertWithdrawal(tx *sqlx.Tx, userID int64, address string, amount float64) error {
	rawSQL := "INSERT INTO withdrawals (`user_id`, `address`, `amount`) VALUES (?, ?, ?)"
	if _, err := tx.Exec(rawSQL, userID, address, amount); err != nil {
		return fmt.Errorf("create withdrawal error: %v", err)
	}

	return nil
}

// GetWithdrawals get user's withdrawal
func (s Storage) GetWithdrawals(userID int64, limit, offset int64) ([]models.Withdrawal, error) {
	rawSQL := "SELECT * FROM `withdrawals` WHERE `user_id` = ? ORDER BY `id` DESC LIMIT ? OFFSET ?"
	args := []interface{}{userID, limit, offset}
	dest := []models.Withdrawal{}
	err := s.selects(&dest, rawSQL, args...)
	return dest, err
}

// GetNumberOfWithdrawals gets number of user's withdrawals
func (s Storage) GetNumberOfWithdrawals(userID int64) (int64, error) {
	var count int64
	err := s.db.QueryRowx("SELECT COUNT(*) FROM `withdrawals` WHERE `user_id` = ?", userID).Scan(&count)
	return count, err
}

// GetPendingWithdrawals get all unprocessed withdrawals
func (s Storage) GetPendingWithdrawals() ([]models.Withdrawal, error) {
	rawSQL := "SELECT * FROM withdrawals WHERE `status` = ? ORDER BY `id` asc"
	args := []interface{}{models.WithdrawalStatusPending}
	dest := []models.Withdrawal{}
	err := s.selects(&dest, rawSQL, args...)
	return dest, err
}

// UpdateWithdrawalStatusToProcessing update withdrawal status to processing if status = pending
func (s Storage) UpdateWithdrawalStatusToProcessing(ids []int64) error {
	rawSQL, args, err := sqlx.In(
		"UPDATE `withdrawals` SET `status` = ? WHERE `id` IN (?) AND `status` = ?",
		models.WithdrawalStatusProcessing,
		ids,
		models.WithdrawalStatusPending,
	)
	if err != nil {
		return fmt.Errorf("update withdrawal status to processing build sql with in: %v", err)
	}

	logrus.WithFields(logrus.Fields{
		"sql":  rawSQL,
		"args": args,
	}).Info("sql update withdrawals status to processing")

	result, err := s.db.Exec(rawSQL, args...)
	if err != nil {
		return err
	}

	if rowAffected, _ := result.RowsAffected(); rowAffected != int64(len(ids)) {
		return fmt.Errorf("expected %v but %v rows affected", len(ids), rowAffected)
	}

	return nil
}

// UpdateWithdrawalStatusToProcessed update withdrawal status to processed if status = processing
func (s Storage) UpdateWithdrawalStatusToProcessed(ids []int64, transactionID string) error {
	rawSQL, args, err := sqlx.In(
		"UPDATE `withdrawals` SET `status` = ?, `transaction_id` = ? WHERE `id` IN (?) AND `status` = ?",
		models.WithdrawalStatusProcessed,
		transactionID,
		ids,
		models.WithdrawalStatusProcessing,
	)
	if err != nil {
		return fmt.Errorf("update withdrawal status to processed build sql with in: %v", err)
	}

	logrus.WithFields(logrus.Fields{
		"sql":  rawSQL,
		"args": args,
	}).Info("sql update withdrawals status to processed")

	result, err := s.db.Exec(rawSQL, args...)
	if err != nil {
		return err
	}

	if rowAffected, _ := result.RowsAffected(); rowAffected != int64(len(ids)) {
		return fmt.Errorf("expected %v but %v rows affected", len(ids), rowAffected)
	}

	return nil
}
<|endoftext|>


<commit_before>package application

import (
	"time"

	"github.com/marcusolsson/goddd/domain/cargo"
	"github.com/marcusolsson/goddd/domain/location"
	"github.com/marcusolsson/goddd/domain/voyage"
)

type HandlingEventService interface {
	RegisterHandlingEvent(completionTime time.Time, trackingId cargo.TrackingId, voyageNumber voyage.VoyageNumber,
		unLocode location.UNLocode, eventType cargo.HandlingEventType) error
}

type handlingEventService struct {
	handlingEventRepository cargo.HandlingEventRepository
	handlingEventFactory    cargo.HandlingEventFactory
	eventHandler            EventHandler
}

func (s *handlingEventService) RegisterHandlingEvent(completionTime time.Time, trackingId cargo.TrackingId, voyageNumber voyage.VoyageNumber,
	unLocode location.UNLocode, eventType cargo.HandlingEventType) error {

	registrationTime := time.Now()
	event, err := s.handlingEventFactory.CreateHandlingEvent(registrationTime, completionTime, trackingId, voyageNumber, unLocode, eventType)

	if err != nil {
		return err
	}

	s.handlingEventRepository.Store(event)

	s.eventHandler.CargoWasHandled(event)

	return nil
}
<commit_msg>Add factory for handling event service.<commit_after>package application

import (
	"time"

	"github.com/marcusolsson/goddd/domain/cargo"
	"github.com/marcusolsson/goddd/domain/location"
	"github.com/marcusolsson/goddd/domain/voyage"
)

type HandlingEventService interface {
	RegisterHandlingEvent(completionTime time.Time, trackingId cargo.TrackingId, voyageNumber voyage.VoyageNumber,
		unLocode location.UNLocode, eventType cargo.HandlingEventType) error
}

type handlingEventService struct {
	handlingEventRepository cargo.HandlingEventRepository
	handlingEventFactory    cargo.HandlingEventFactory
	eventHandler            EventHandler
}

func (s *handlingEventService) RegisterHandlingEvent(completionTime time.Time, trackingId cargo.TrackingId, voyageNumber voyage.VoyageNumber,
	unLocode location.UNLocode, eventType cargo.HandlingEventType) error {

	registrationTime := time.Now()
	event, err := s.handlingEventFactory.CreateHandlingEvent(registrationTime, completionTime, trackingId, voyageNumber, unLocode, eventType)

	if err != nil {
		return err
	}

	s.handlingEventRepository.Store(event)

	s.eventHandler.CargoWasHandled(event)

	return nil
}

func NewHandlingEventService(r cargo.HandlingEventRepository, f cargo.HandlingEventFactory, h EventHandler) HandlingEventService {
	return &handlingEventService{
		handlingEventRepository: r,
		handlingEventFactory:    f,
		eventHandler:            h,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/url"
	"time"

	cfclient "github.com/cloudfoundry-community/go-cfclient"
)

func QuotaGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}

		reservedMemory := 0
		reservedServices := 0
		allocatedMemory := 0
		allocatedServices := 0
		reservedRoutes := 0

		for _, org := range orgs {
			quota, err := org.Quota()
			if err != nil {
				return err
			}
			reservedMemory += quota.MemoryLimit
			reservedServices += quota.TotalServices
			reservedRoutes += quota.TotalRoutes
		}

		apps, err := c.cf.ListApps()
		if err != nil {
			return err
		}
		for _, app := range apps {
			allocatedMemory += (app.Memory * app.Instances)
		}

		allocatedServices, err = c.CountServiceInstances()
		if err != nil {
			return err
		}

		return w.WriteMetrics([]Metric{
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.services.reserved", // number of services reserved by quotas
				Value: float64(reservedServices),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.services.allocated", // number of services in use
				Value: float64(allocatedServices),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.memory.reserved", // memory reserved by org quotas
				Value: float64(reservedMemory),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.memory.allocated", // memory allocated to apps
				Value: float64(allocatedMemory),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.routes.reserved", // number of routes reserved
				Value: float64(reservedRoutes),
			},
		})
	})
}

func UserCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		// global auditor role cannot use /v2/users
		// so we have to fetch users from each org
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}

		userGuids := map[string]bool{}

		for _, org := range orgs {
			users, err := c.OrgUsers(org.Guid)
			if err != nil {
				return err
			}
			for _, u := range users {
				userGuids[u.Guid] = true
			}
		}

		return w.WriteMetrics([]Metric{
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "users.count",
				Value: float64(len(userGuids)),
			},
		})
	})
}

func AppCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		apps, err := c.cf.ListApps()
		if err != nil {
			return err
		}
		spaces, err := c.cf.ListSpaces()
		if err != nil {
			return err
		}
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}
		org_quotas, err := c.cf.ListOrgQuotas()
		if err != nil {
			return err
		}

		// Number of relevant apps in
		// - APP_STATE: string of whether each app is "started" or "stopped"
		// - ORG_IS_TRIAL: boolean of whether each app is owned by a trial organisation
		// counters[APP_STATE][ORG_IS_TRIAL]
		counters := map[string]map[bool]int{
			"started": map[bool]int{},
			"stopped": map[bool]int{},
		}
		for _, app := range apps {
			space := findSpace(spaces, app.SpaceGuid)
			if space == nil {
				log.Printf("Space was not found for app %s\n", app.Guid)
				continue
			}
			org := findOrg(orgs, space.OrganizationGuid)
			if org == nil {
				log.Printf("Org was not found for app %s in space %s\n", app.Guid, space.Guid)
				continue
			}
			org_quota := findOrgQuota(org_quotas, org.QuotaDefinitionGuid)
			if org_quota == nil {
				log.Printf("Org Quota was not found for app %s in org %s\n", app.Guid, org.Guid)
				continue
			}
			org_is_trial := isOrgQuotaTrial(org_quota)
			if app.State == "STARTED" {
				counters["started"][org_is_trial]++
			}
			if app.State == "STOPPED" {
				counters["stopped"][org_is_trial]++
			}
		}

		metrics := []Metric{}
		for state, count_by_trial := range counters {
			for org_is_trial, count := range count_by_trial {
				metrics = append(metrics, Metric{
					Kind:  Gauge,
					Time:  time.Now(),
					Name:  "apps.count",
					Value: float64(count),
					Tags: []string{
						"state:" + state,
						fmt.Sprintf("trial_org:%t", org_is_trial),
					},
				})
			}
		}
		return w.WriteMetrics(metrics)
	})
}

func ServiceCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		serviceInstances, err := c.cf.ListServiceInstances()
		if err != nil {
			return err
		}
		services, err := c.cf.ListServices()
		if err != nil {
			return nil
		}
		service_plans, err := c.cf.ListServicePlans()
		if err != nil {
			return nil
		}
		spaces, err := c.cf.ListSpaces()
		if err != nil {
			return err
		}
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}
		org_quotas, err := c.cf.ListOrgQuotas()
		if err != nil {
			return err
		}

		// Number of relevant service instances in
		// - ORG_IS_TRIAL: boolean of whether each instance is owned by a trial organisation
		// - SERVICE_PLAN_IS_FREE: whether the instance's service plan is free
		// - NAME_OF_SERVICE: e.g., "mysql" or "postgres"
		// counters[ORG_IS_TRIAL][SERVICE_PLAN_IS_FREE][NAME_OF_SERVICE]
		counters := map[bool]map[bool]map[string]int{
			true: map[bool]map[string]int{
				true:  map[string]int{},
				false: map[string]int{},
			},
			false: map[bool]map[string]int{
				true:  map[string]int{},
				false: map[string]int{},
			},
		}
		for _, instance := range serviceInstances {
			service := findService(services, instance.ServiceGuid)
			if service == nil {
				log.Printf("Service was not found for service instance %s\n", instance.Guid)
				continue
			}
			if service.Label == "" {
				log.Printf("Service label was empty for service %s and service instance %s\n", service.Guid, instance.Guid)
				continue
			}
			service_plan := findServicePlan(service_plans, instance.ServicePlanGuid)
			if service_plan == nil {
				log.Printf("Error finding service plan for service instance %s: %s\n", instance.Guid, err)
				continue
			}
			space := findSpace(spaces, instance.SpaceGuid)
			if space == nil {
				log.Printf("Space was not found for service instance %s\n", instance.Guid)
				continue
			}
			org := findOrg(orgs, space.OrganizationGuid)
			if org == nil {
				log.Printf("Org was not found for service instance %s in space %s\n", instance.Guid, space.Guid)
				continue
			}
			org_quota := findOrgQuota(org_quotas, org.QuotaDefinitionGuid)
			if err != nil {
				log.Printf("Org Quota was not found for service instance %s in org %s\n", instance.Guid, org.Guid)
				continue
			}
			org_is_trial := isOrgQuotaTrial(org_quota)
			service_plan_is_free := isServicePlanFree(service_plan)
			counters[org_is_trial][service_plan_is_free][service.Label]++
		}

		metrics := []Metric{}
		for org_is_trial, x := range counters {
			for service_plan_is_free, y := range x {
				for service_label, count := range y {
					metrics = append(metrics, Metric{
						Kind:  Gauge,
						Time:  time.Now(),
						Name:  "services.provisioned",
						Value: float64(count),
						Tags: []string{
							"type:" + service_label,
							fmt.Sprintf("trial_org:%t", org_is_trial),
							fmt.Sprintf("free_service:%t", service_plan_is_free),
						},
					})
				}
			}
		}
		return w.WriteMetrics(metrics)
	})
}

func OrgCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}
		counters := map[string]int{}
		for _, org := range orgs {
			quota, err := org.Quota()
			if err != nil {
				log.Printf("Error finding org quota for org %s: %s\n", org.Guid, err)
				continue
			}
			counters[quota.Name]++
		}
		metrics := []Metric{}
		for name, count := range counters {
			metrics = append(metrics, Metric{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "orgs.count",
				Value: float64(count),
				Tags:  []string{"quota:" + name},
			})
		}
		return w.WriteMetrics(metrics)
	})
}

func SpaceCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		spaces, err := c.cf.ListSpaces()
		if err != nil {
			return err
		}
		return w.WriteMetrics([]Metric{
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "spaces.count",
				Value: float64(len(spaces)),
			},
		})
	})
}

func EventCountGauge(c *Client, eventType string, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		u, err := url.Parse("/v2/events")
		if err != nil {
			return err
		}
		maxAge := time.Now().Add(-1 * interval)
		q := u.Query()
		q.Set("order-direction", "desc")
		q.Set("results-per-page", "100")
		q.Add("q", "type:"+eventType)
		q.Add("q", "timestamp>"+maxAge.Format(time.RFC3339Nano))
		u.RawQuery = q.Encode()
		batchUrl := u.String()
		gauge := Metric{
			Time: time.Now(),
			Kind: Gauge,
			Name: "events." + eventType,
		}
		for batchUrl != "" {
			var batch struct {
				NextUrl   string          `json:"next_url"`
				Resources []AppUsageEvent `json:"resources"`
			}
			if err := c.get(batchUrl, &batch); err != nil {
				return err
			}
			for _, res := range batch.Resources {
				if res.MetaData.CreatedAt.Before(maxAge) {
					break
				}
				gauge.Value += 1
			}
			batchUrl = batch.NextUrl
		}
		return w.WriteMetrics([]Metric{gauge})
	})
}

func findService(services []cfclient.Service, guid string) *cfclient.Service {
	for _, service := range services {
		if service.Guid == guid {
			return &service
		}
	}
	return nil
}

func findServicePlan(service_plans []cfclient.ServicePlan, guid string) *cfclient.ServicePlan {
	for _, service_plan := range service_plans {
		if service_plan.Guid == guid {
			return &service_plan
		}
	}
	return nil
}

func findSpace(spaces []cfclient.Space, guid string) *cfclient.Space {
	for _, space := range spaces {
		if space.Guid == guid {
			return &space
		}
	}
	return nil
}

func findOrg(orgs []cfclient.Org, guid string) *cfclient.Org {
	for _, org := range orgs {
		if org.Guid == guid {
			return &org
		}
	}
	return nil
}

func findOrgQuota(org_quotas []cfclient.OrgQuota, guid string) *cfclient.OrgQuota {
	for _, org_quota := range org_quotas {
		if org_quota.Guid == guid {
			return &org_quota
		}
	}
	return nil
}

// Determine if an organisation is on a trial plan.
func isOrgQuotaTrial(quota *cfclient.OrgQuota) bool {
	return quota.Name == "default"
}

// Determine if a service plan is free.
func isServicePlanFree(plan *cfclient.ServicePlan) bool {
	return plan.Name == "Free"
}
<commit_msg>fixup! Tag app and service metrics with free/paid status<commit_after>package main

import (
	"fmt"
	"log"
	"net/url"
	"time"

	cfclient "github.com/cloudfoundry-community/go-cfclient"
)

func QuotaGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}

		reservedMemory := 0
		reservedServices := 0
		allocatedMemory := 0
		allocatedServices := 0
		reservedRoutes := 0

		for _, org := range orgs {
			quota, err := org.Quota()
			if err != nil {
				return err
			}
			reservedMemory += quota.MemoryLimit
			reservedServices += quota.TotalServices
			reservedRoutes += quota.TotalRoutes
		}

		apps, err := c.cf.ListApps()
		if err != nil {
			return err
		}
		for _, app := range apps {
			allocatedMemory += (app.Memory * app.Instances)
		}

		allocatedServices, err = c.CountServiceInstances()
		if err != nil {
			return err
		}

		return w.WriteMetrics([]Metric{
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.services.reserved", // number of services reserved by quotas
				Value: float64(reservedServices),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.services.allocated", // number of services in use
				Value: float64(allocatedServices),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.memory.reserved", // memory reserved by org quotas
				Value: float64(reservedMemory),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.memory.allocated", // memory allocated to apps
				Value: float64(allocatedMemory),
			},
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "quota.routes.reserved", // number of routes reserved
				Value: float64(reservedRoutes),
			},
		})
	})
}

func UserCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		// global auditor role cannot use /v2/users
		// so we have to fetch users from each org
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}

		userGuids := map[string]bool{}

		for _, org := range orgs {
			users, err := c.OrgUsers(org.Guid)
			if err != nil {
				return err
			}
			for _, u := range users {
				userGuids[u.Guid] = true
			}
		}

		return w.WriteMetrics([]Metric{
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "users.count",
				Value: float64(len(userGuids)),
			},
		})
	})
}

func AppCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		apps, err := c.cf.ListApps()
		if err != nil {
			return err
		}
		spaces, err := c.cf.ListSpaces()
		if err != nil {
			return err
		}
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}
		orgQuotas, err := c.cf.ListOrgQuotas()
		if err != nil {
			return err
		}

		// Number of relevant apps in
		// - APP_STATE: string of whether each app is "started" or "stopped"
		// - ORG_IS_TRIAL: boolean of whether each app is owned by a trial organisation
		// counters[APP_STATE][ORG_IS_TRIAL]
		counters := map[string]map[bool]int{
			"started": map[bool]int{},
			"stopped": map[bool]int{},
		}
		for _, app := range apps {
			space := findSpace(spaces, app.SpaceGuid)
			if space == nil {
				log.Printf("Space was not found for app %s\n", app.Guid)
				continue
			}
			org := findOrg(orgs, space.OrganizationGuid)
			if org == nil {
				log.Printf("Org was not found for app %s in space %s\n", app.Guid, space.Guid)
				continue
			}
			orgQuota := findOrgQuota(orgQuotas, org.QuotaDefinitionGuid)
			if orgQuota == nil {
				log.Printf("Org Quota was not found for app %s in org %s\n", app.Guid, org.Guid)
				continue
			}
			orgIsTrial := isOrgQuotaTrial(orgQuota)
			if app.State == "STARTED" {
				counters["started"][orgIsTrial]++
			}
			if app.State == "STOPPED" {
				counters["stopped"][orgIsTrial]++
			}
		}

		metrics := []Metric{}
		for state, countByTrial := range counters {
			for orgIsTrial, count := range countByTrial {
				metrics = append(metrics, Metric{
					Kind:  Gauge,
					Time:  time.Now(),
					Name:  "apps.count",
					Value: float64(count),
					Tags: []string{
						"state:" + state,
						fmt.Sprintf("trial_org:%t", orgIsTrial),
					},
				})
			}
		}
		return w.WriteMetrics(metrics)
	})
}

func ServiceCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		serviceInstances, err := c.cf.ListServiceInstances()
		if err != nil {
			return err
		}
		services, err := c.cf.ListServices()
		if err != nil {
			return nil
		}
		servicePlans, err := c.cf.ListServicePlans()
		if err != nil {
			return nil
		}
		spaces, err := c.cf.ListSpaces()
		if err != nil {
			return err
		}
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}
		orgQuotas, err := c.cf.ListOrgQuotas()
		if err != nil {
			return err
		}

		// Number of relevant service instances in
		// - ORG_IS_TRIAL: boolean of whether each instance is owned by a trial organisation
		// - SERVICE_PLAN_IS_FREE: whether the instance's service plan is free
		// - NAME_OF_SERVICE: e.g., "mysql" or "postgres"
		// counters[ORG_IS_TRIAL][SERVICE_PLAN_IS_FREE][NAME_OF_SERVICE]
		counters := map[bool]map[bool]map[string]int{
			true: map[bool]map[string]int{
				true:  map[string]int{},
				false: map[string]int{},
			},
			false: map[bool]map[string]int{
				true:  map[string]int{},
				false: map[string]int{},
			},
		}
		for _, instance := range serviceInstances {
			service := findService(services, instance.ServiceGuid)
			if service == nil {
				log.Printf("Service was not found for service instance %s\n", instance.Guid)
				continue
			}
			if service.Label == "" {
				log.Printf("Service label was empty for service %s and service instance %s\n", service.Guid, instance.Guid)
				continue
			}
			servicePlan := findServicePlan(servicePlans, instance.ServicePlanGuid)
			if servicePlan == nil {
				log.Printf("Error finding service plan for service instance %s: %s\n", instance.Guid, err)
				continue
			}
			space := findSpace(spaces, instance.SpaceGuid)
			if space == nil {
				log.Printf("Space was not found for service instance %s\n", instance.Guid)
				continue
			}
			org := findOrg(orgs, space.OrganizationGuid)
			if org == nil {
				log.Printf("Org was not found for service instance %s in space %s\n", instance.Guid, space.Guid)
				continue
			}
			orgQuota := findOrgQuota(orgQuotas, org.QuotaDefinitionGuid)
			if err != nil {
				log.Printf("Org Quota was not found for service instance %s in org %s\n", instance.Guid, org.Guid)
				continue
			}
			orgIsTrial := isOrgQuotaTrial(orgQuota)
			servicePlanIsFree := isServicePlanFree(servicePlan)
			counters[orgIsTrial][servicePlanIsFree][service.Label]++
		}

		metrics := []Metric{}
		for orgIsTrial, x := range counters {
			for servicePlanIsFree, y := range x {
				for serviceLabel, count := range y {
					metrics = append(metrics, Metric{
						Kind:  Gauge,
						Time:  time.Now(),
						Name:  "services.provisioned",
						Value: float64(count),
						Tags: []string{
							"type:" + serviceLabel,
							fmt.Sprintf("trial_org:%t", orgIsTrial),
							fmt.Sprintf("free_service:%t", servicePlanIsFree),
						},
					})
				}
			}
		}
		return w.WriteMetrics(metrics)
	})
}

func OrgCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		orgs, err := c.cf.ListOrgs()
		if err != nil {
			return err
		}
		counters := map[string]int{}
		for _, org := range orgs {
			quota, err := org.Quota()
			if err != nil {
				log.Printf("Error finding org quota for org %s: %s\n", org.Guid, err)
				continue
			}
			counters[quota.Name]++
		}
		metrics := []Metric{}
		for name, count := range counters {
			metrics = append(metrics, Metric{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "orgs.count",
				Value: float64(count),
				Tags:  []string{"quota:" + name},
			})
		}
		return w.WriteMetrics(metrics)
	})
}

func SpaceCountGauge(c *Client, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		spaces, err := c.cf.ListSpaces()
		if err != nil {
			return err
		}
		return w.WriteMetrics([]Metric{
			{
				Kind:  Gauge,
				Time:  time.Now(),
				Name:  "spaces.count",
				Value: float64(len(spaces)),
			},
		})
	})
}

func EventCountGauge(c *Client, eventType string, interval time.Duration) MetricReadCloser {
	return NewMetricPoller(interval, func(w MetricWriter) error {
		u, err := url.Parse("/v2/events")
		if err != nil {
			return err
		}
		maxAge := time.Now().Add(-1 * interval)
		q := u.Query()
		q.Set("order-direction", "desc")
		q.Set("results-per-page", "100")
		q.Add("q", "type:"+eventType)
		q.Add("q", "timestamp>"+maxAge.Format(time.RFC3339Nano))
		u.RawQuery = q.Encode()
		batchUrl := u.String()
		gauge := Metric{
			Time: time.Now(),
			Kind: Gauge,
			Name: "events." + eventType,
		}
		for batchUrl != "" {
			var batch struct {
				NextUrl   string          `json:"next_url"`
				Resources []AppUsageEvent `json:"resources"`
			}
			if err := c.get(batchUrl, &batch); err != nil {
				return err
			}
			for _, res := range batch.Resources {
				if res.MetaData.CreatedAt.Before(maxAge) {
					break
				}
				gauge.Value += 1
			}
			batchUrl = batch.NextUrl
		}
		return w.WriteMetrics([]Metric{gauge})
	})
}

func findService(services []cfclient.Service, guid string) *cfclient.Service {
	for _, service := range services {
		if service.Guid == guid {
			return &service
		}
	}
	return nil
}

func findServicePlan(servicePlans []cfclient.ServicePlan, guid string) *cfclient.ServicePlan {
	for _, servicePlan := range servicePlans {
		if servicePlan.Guid == guid {
			return &servicePlan
		}
	}
	return nil
}

func findSpace(spaces []cfclient.Space, guid string) *cfclient.Space {
	for _, space := range spaces {
		if space.Guid == guid {
			return &space
		}
	}
	return nil
}

func findOrg(orgs []cfclient.Org, guid string) *cfclient.Org {
	for _, org := range orgs {
		if org.Guid == guid {
			return &org
		}
	}
	return nil
}

func findOrgQuota(orgQuotas []cfclient.OrgQuota, guid string) *cfclient.OrgQuota {
	for _, orgQuota := range orgQuotas {
		if orgQuota.Guid == guid {
			return &orgQuota
		}
	}
	return nil
}

// Determine if an organisation is on a trial plan.
func isOrgQuotaTrial(quota *cfclient.OrgQuota) bool {
	return quota.Name == "default"
}

// Determine if a service plan is free.
func isServicePlanFree(plan *cfclient.ServicePlan) bool {
	return plan.Name == "Free"
}
<|endoftext|>


<commit_before>package khukuri

import (
	"encoding/json"
	"fmt"
	"github.com/fzzy/radix/redis"
	"github.com/gorilla/mux"
	"log"
	"net/http"
)

type UrlMsg struct {
	Url          string `json:shorturl`
	ErrorMessage string `json:errorMessage`
}

var hostRedis string = ""
var portRedis string = ""
var timeOutRedis int = 10

func RegisterAndStart(redisHost string, redisPort string, serverPort string, timeOutSeconds int) {
	hostRedis = redisHost
	portRedis = redisPort
	timeOutRedis = timeOutSeconds
	serverPort = ":" + serverPort

	r := mux.NewRouter()
	r.HandleFunc("/", RootHandler)

	shortner := r.Path("/{alias}").Subrouter()
	shortner.Methods("GET").HandlerFunc(RedirectToBaseHandler)

	api := r.PathPrefix("/api/v1/").Subrouter()
	api.Methods("GET").MatcherFunc(AliasMatcher).HandlerFunc(AliasHandler)
	api.Methods("POST").MatcherFunc(BaseMatcher).HandlerFunc(BaseHandler)

	log.Println("Server starting on port ", serverPort)
	log.Fatal(http.ListenAndServe(serverPort, r))

}

func RootHandler(rw http.ResponseWriter, r *http.Request) {
	fmt.Fprint(rw, "Welcome to the gorkha URL shortner v0.01")
}

func RedirectToBaseHandler(rw http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	c, err := SetupRedisConnection(hostRedis, portRedis, timeOutRedis)
	if err != nil {
		log.Fatal("Unable to setup a redis connection", err)
	}

	baseUrl, ok := LookupAlias(vars["alias"], c)
	if ok != nil {
		log.Println("Error while redirecting")
		log.Println(ok)
		http.NotFound(rw, r)
	}
	if baseUrl != "" {
		http.Redirect(rw, r, baseUrl, http.StatusMovedPermanently)
	} else {
		http.NotFound(rw, r)
	}

}

func AliasHandler(rw http.ResponseWriter, r *http.Request) {

	c, err := SetupRedisConnection(hostRedis, portRedis, timeOutRedis)
	if err != nil {
		log.Fatal("Unable to setup a redis connection", err)
	}

	baseUrl, ok := ExtractBaseUrl(r, c)
	urlMessage := UrlMsg{"", ""}
	if ok == nil {
		urlMessage = UrlMsg{baseUrl, ""}
	} else {
		urlMessage = UrlMsg{baseUrl, ok.Error()}
	}
	js, _ := json.Marshal(urlMessage)
	rw.Header().Set("Content-Type", "application/json")
	rw.Write(js)
}

func ExtractBaseUrl(r *http.Request, c *redis.Client) (string, error) {
	alias := extractParam(r, "alias")
	return LookupAlias(alias, c)
}

func BaseHandler(rw http.ResponseWriter, r *http.Request) {

	c, err := SetupRedisConnection(hostRedis, portRedis, timeOutRedis)
	if err != nil {
		log.Fatal("Unable to setup a redis connection", err)
	}

	baseUrl := extractParam(r, "base")
	alias, ok := StoreUrl(baseUrl, c)
	if ok != nil {
		http.Error(rw, ok.Error(), http.StatusInternalServerError)
	} else {
		urlMessage := UrlMsg{alias, ""}
		js, _ := json.Marshal(urlMessage)
		rw.Header().Set("Content-Type", "application/json")
		rw.Write(js)
	}
}

func AliasMatcher(r *http.Request, rm *mux.RouteMatch) bool {
	queryParams := r.URL.Query()
	return queryParams.Get("alias") != ""
}

func BaseMatcher(r *http.Request, rm *mux.RouteMatch) bool {
	queryParams := r.URL.Query()
	return queryParams.Get("base") != ""
}

func MultiMatcher(r *http.Request, rm *mux.RouteMatch) bool {
	return AliasMatcher(r, rm) && BaseMatcher(r, rm)
}

func extractParam(r *http.Request, a string) string {
	queryPrams := r.URL.Query()
	return queryPrams.Get(a)
}
<commit_msg>Adding logging<commit_after>package khukuri

import (
	"encoding/json"
	"fmt"
	"github.com/fzzy/radix/redis"
	"github.com/gorilla/mux"
	"log"
	"net/http"
)

type UrlMsg struct {
	Url          string `json:shorturl`
	ErrorMessage string `json:errorMessage`
}

var hostRedis string = ""
var portRedis string = ""
var timeOutRedis int = 10

func RegisterAndStart(redisHost string, redisPort string, serverPort string, timeOutSeconds int) {
	hostRedis = redisHost
	portRedis = redisPort
	timeOutRedis = timeOutSeconds
	serverPort = ":" + serverPort

	r := mux.NewRouter()
	r.HandleFunc("/", RootHandler)

	shortner := r.Path("/{alias}").Subrouter()
	shortner.Methods("GET").HandlerFunc(RedirectToBaseHandler)

	api := r.PathPrefix("/api/v1/").Subrouter()
	api.Methods("GET").MatcherFunc(AliasMatcher).HandlerFunc(AliasHandler)
	api.Methods("POST").MatcherFunc(BaseMatcher).HandlerFunc(BaseHandler)

	log.Println("Server starting on port ", serverPort)
	log.Fatal(http.ListenAndServe(serverPort, r))

}

func RootHandler(rw http.ResponseWriter, r *http.Request) {
	log.Println(r.UserAgent(), "  ", r.Method)
	fmt.Fprint(rw, "Welcome to the gohort URL shortner v0.01")
}

func RedirectToBaseHandler(rw http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	log.Println(r.UserAgent(), "  ", r.Method, r.URL)
	c, err := SetupRedisConnection(hostRedis, portRedis, timeOutRedis)
	if err != nil {
		log.Fatal("Unable to setup a redis connection", err)
	}

	baseUrl, ok := LookupAlias(vars["alias"], c)
	if ok != nil {
		log.Println("Error while redirecting")
		log.Println(ok)
		http.NotFound(rw, r)
	}
	if baseUrl != "" {
		http.Redirect(rw, r, baseUrl, http.StatusMovedPermanently)
	} else {
		http.NotFound(rw, r)
	}

}

func AliasHandler(rw http.ResponseWriter, r *http.Request) {
	log.Println(r.UserAgent(), "  ", r.Method, r.URL)
	c, err := SetupRedisConnection(hostRedis, portRedis, timeOutRedis)
	if err != nil {
		log.Fatal("Unable to setup a redis connection", err)
	}

	baseUrl, ok := ExtractBaseUrl(r, c)
	urlMessage := UrlMsg{"", ""}
	if ok == nil {
		urlMessage = UrlMsg{baseUrl, ""}
	} else {
		urlMessage = UrlMsg{baseUrl, ok.Error()}
	}
	js, _ := json.Marshal(urlMessage)
	rw.Header().Set("Content-Type", "application/json")
	rw.Write(js)
}

func ExtractBaseUrl(r *http.Request, c *redis.Client) (string, error) {
	alias := extractParam(r, "alias")
	return LookupAlias(alias, c)
}

func BaseHandler(rw http.ResponseWriter, r *http.Request) {
	log.Println(r.UserAgent(), "  ", r.Method, r.URL)
	c, err := SetupRedisConnection(hostRedis, portRedis, timeOutRedis)
	if err != nil {
		log.Fatal("Unable to setup a redis connection", err)
	}

	baseUrl := extractParam(r, "base")
	alias, ok := StoreUrl(baseUrl, c)
	if ok != nil {
		http.Error(rw, ok.Error(), http.StatusInternalServerError)
	} else {
		urlMessage := UrlMsg{alias, ""}
		js, _ := json.Marshal(urlMessage)
		rw.Header().Set("Content-Type", "application/json")
		rw.Write(js)
	}
}

func AliasMatcher(r *http.Request, rm *mux.RouteMatch) bool {
	queryParams := r.URL.Query()
	return queryParams.Get("alias") != ""
}

func BaseMatcher(r *http.Request, rm *mux.RouteMatch) bool {
	queryParams := r.URL.Query()
	return queryParams.Get("base") != ""
}

func MultiMatcher(r *http.Request, rm *mux.RouteMatch) bool {
	return AliasMatcher(r, rm) && BaseMatcher(r, rm)
}

func extractParam(r *http.Request, a string) string {
	queryPrams := r.URL.Query()
	return queryPrams.Get(a)
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package monitor

import (
	"sync"
	"time"
	"errors"

	"github.com/nanopack/yoke/state"
	"github.com/nanopack/yoke/config"
)

var (
	ClusterUnaviable = errors.New("none of the nodes in the cluster are available")
)

type (
	Looper interface {
		Loop(time.Duration) error
	}

	decider struct {
		sync.Mutex

		me        state.State
		other     state.State
		monitor   state.State
		performer Performer
	}
)

func NewDecider(me, other, monitor state.State, performer Performer) Looper {
	decider := decider{
		me:        me,
		other:     other,
		monitor:   monitor,
		performer: performer,
	}
	for {
		// Really we only have to wait for a quorum, 2 out of 3 will allow everything to be ok.
		// But in certain conditions, this node was a backup that was down, and the current active
		// if offline, we need to wait for all 3 nodes.
		// So really we are going to wait for all 3 nodes to make it simple
		// me is already Ready. no need to call it
		config.Log.Info("waiting for cluster to be ready")
		other.Ready()
		monitor.Ready()
		config.Log.Info("cluster is ready")

		err := decider.reCheck()
		switch err {
		case ClusterUnaviable: // we try again.
		case nil: // the cluster was successfully rechecked
			return decider
		default:
			config.Log.Fatal("Another kind of error occured: %v", err)
			os.Exit(1)
		}
	}
}

// this is the main loop for monitoring the cluster and making any changes needed to
// reflect changes in remote nodes in the cluster
func (decider decider) Loop(check time.Duration) error {
	timer := time.Tick(check)
	for range timer {
		err := decider.reCheck()
		switch {
		case err == ClusterUnaviable:
		case err != nil:
			return err
		default:
		}
	}
	return nil
}

// this is used to move a active node to a backup node
func (decider decider) Demote() {
	decider.Lock()
	defer decider.Unlock()

	decider.performer.TransitionToBackup()
}

// this is used to move a backup node to an active node
func (decider decider) Promote() {
	decider.Lock()
	defer decider.Unlock()

	decider.performer.TransitionToActive()
}

// Checks the other node in the cluster, falling back to bouncing the check off of the monitor,
// to see if the states between this node and the remote node match up
func (decider decider) reCheck() error {
	decider.Lock()
	defer decider.Unlock()

	var otherDBRole string
	var err error
	config.Log.Info("checking other role")
	otherDBRole, err = decider.other.GetDBRole()
	if err != nil {
		config.Log.Info("checking other role (bounce)")
		address := decider.other.Location()
		otherDBRole, err = decider.monitor.Bounce(address).GetDBRole()
		if err != nil {
			// this node can't talk to the other member of the cluster or the monitor
			// if this node is not in single mode it needs to shut off
			if role, err := decider.me.GetDBRole(); role != "single" || err != nil {
				config.Log.Info("stopping, no one here")
				decider.performer.Stop()
				return ClusterUnaviable
			}
			return nil
		}
	}

	config.Log.Info("other node is '%v'", otherDBRole)

	// we need to handle multiple possible states that the remote node is in
	switch otherDBRole {
	case "single":
		fallthrough
	case "active":
		decider.performer.TransitionToBackup()
	case "dead":
		DBrole, err := decider.me.GetDBRole()
		if err != nil {
			return err
		}
		if DBrole == "backup" {
			// if this node is not synced up to the previous master, then we must wait for the other node to
			// come online
			hasSynced, err := decider.me.HasSynced()
			if err != nil {
				return err
			}
			if !hasSynced {
				decider.performer.Stop()
				return ClusterUnaviable
			}
		}

		decider.performer.TransitionToSingle()
	case "initialized":
		role, err := decider.me.GetRole()
		if err != nil {
			return err
		}
		switch role {
		case "primary":
			decider.performer.TransitionToActive()
		case "secondary":
			decider.performer.TransitionToBackup()
		}
	case "backup":
		decider.performer.TransitionToActive()
	}
	return nil
}
<commit_msg>import os<commit_after>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package monitor

import (
	"sync"
	"time"
	"errors"
	"os"

	"github.com/nanopack/yoke/state"
	"github.com/nanopack/yoke/config"
)

var (
	ClusterUnaviable = errors.New("none of the nodes in the cluster are available")
)

type (
	Looper interface {
		Loop(time.Duration) error
	}

	decider struct {
		sync.Mutex

		me        state.State
		other     state.State
		monitor   state.State
		performer Performer
	}
)

func NewDecider(me, other, monitor state.State, performer Performer) Looper {
	decider := decider{
		me:        me,
		other:     other,
		monitor:   monitor,
		performer: performer,
	}
	for {
		// Really we only have to wait for a quorum, 2 out of 3 will allow everything to be ok.
		// But in certain conditions, this node was a backup that was down, and the current active
		// if offline, we need to wait for all 3 nodes.
		// So really we are going to wait for all 3 nodes to make it simple
		// me is already Ready. no need to call it
		config.Log.Info("waiting for cluster to be ready")
		other.Ready()
		monitor.Ready()
		config.Log.Info("cluster is ready")

		err := decider.reCheck()
		switch err {
		case ClusterUnaviable: // we try again.
		case nil: // the cluster was successfully rechecked
			return decider
		default:
			config.Log.Fatal("Another kind of error occured: %v", err)
			os.Exit(1)
		}
	}
}

// this is the main loop for monitoring the cluster and making any changes needed to
// reflect changes in remote nodes in the cluster
func (decider decider) Loop(check time.Duration) error {
	timer := time.Tick(check)
	for range timer {
		err := decider.reCheck()
		switch {
		case err == ClusterUnaviable:
		case err != nil:
			return err
		default:
		}
	}
	return nil
}

// this is used to move a active node to a backup node
func (decider decider) Demote() {
	decider.Lock()
	defer decider.Unlock()

	decider.performer.TransitionToBackup()
}

// this is used to move a backup node to an active node
func (decider decider) Promote() {
	decider.Lock()
	defer decider.Unlock()

	decider.performer.TransitionToActive()
}

// Checks the other node in the cluster, falling back to bouncing the check off of the monitor,
// to see if the states between this node and the remote node match up
func (decider decider) reCheck() error {
	decider.Lock()
	defer decider.Unlock()

	var otherDBRole string
	var err error
	config.Log.Info("checking other role")
	otherDBRole, err = decider.other.GetDBRole()
	if err != nil {
		config.Log.Info("checking other role (bounce)")
		address := decider.other.Location()
		otherDBRole, err = decider.monitor.Bounce(address).GetDBRole()
		if err != nil {
			// this node can't talk to the other member of the cluster or the monitor
			// if this node is not in single mode it needs to shut off
			if role, err := decider.me.GetDBRole(); role != "single" || err != nil {
				config.Log.Info("stopping, no one here")
				decider.performer.Stop()
				return ClusterUnaviable
			}
			return nil
		}
	}

	config.Log.Info("other node is '%v'", otherDBRole)

	// we need to handle multiple possible states that the remote node is in
	switch otherDBRole {
	case "single":
		fallthrough
	case "active":
		decider.performer.TransitionToBackup()
	case "dead":
		DBrole, err := decider.me.GetDBRole()
		if err != nil {
			return err
		}
		if DBrole == "backup" {
			// if this node is not synced up to the previous master, then we must wait for the other node to
			// come online
			hasSynced, err := decider.me.HasSynced()
			if err != nil {
				return err
			}
			if !hasSynced {
				decider.performer.Stop()
				return ClusterUnaviable
			}
		}

		decider.performer.TransitionToSingle()
	case "initialized":
		role, err := decider.me.GetRole()
		if err != nil {
			return err
		}
		switch role {
		case "primary":
			decider.performer.TransitionToActive()
		case "secondary":
			decider.performer.TransitionToBackup()
		}
	case "backup":
		decider.performer.TransitionToActive()
	}
	return nil
}
<|endoftext|>