<commit_before>package lnwallet

import (
	"errors"
	"fmt"
	"sync"

	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcd/txscript"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"
)

// ErrNotMine is an error denoting that a WalletController instance is unable
// to spend a specifid output.
var ErrNotMine = errors.New("the passed output doesn't belong to the wallet")

// AddressType is a enum-like type which denotes the possible address types
// WalletController supports.
type AddressType uint8

const (
	// WitnessPubKey represents a p2wkh address.
	WitnessPubKey AddressType = iota

	// NestedWitnessPubKey represents a p2sh output which is itself a
	// nested p2wkh output.
	NestedWitnessPubKey

	// PublicKey represents a regular p2pkh output.
	PubKeyHash
)

// Utxo is an unspent output denoted by its outpoint, and output value of the
// original output.
type Utxo struct {
	Value btcutil.Amount
	wire.OutPoint
}

// TransactionDetail describes a transaction with either inputs which belong to
// the wallet, or has outputs that pay to the wallet.
type TransactionDetail struct {
	// Hash is the transaction hash of the transaction.
	Hash wire.ShaHash

	// Value is the net value of this transaction (in satoshis) from the
	// PoV of the wallet. If this transaction purely spends from the
	// wallet's funds, then this value will be negative. Similarly, if this
	// transaction credits the wallet, then this value will be positive.
	Value btcutil.Amount

	// NumConfirmations is the number of confirmations this transaction
	// has. If the transaction is unconfirmed, then this value will be
	// zero.
	NumConfirmations int32

	// BlockHeight is the hash of the block which includes this
	// transaction. Unconfirmed transactions will have a nil value for this
	// field.
	BlockHash *wire.ShaHash

	// BlockHeight is the height of the block including this transaction.
	// Unconfirmed transaction will show a height of zero.
	BlockHeight int32

	// Timestamp is the unix timestamp of the block including this
	// transaction. If the transaction is unconfirmed, then this will be a
	// timestamp of txn creation.
	Timestamp int64

	// TotalFees is the total fee in satoshis paid by this transaction.
	TotalFees int64
}
// WalletController defines an abstract interface for controlling a local Pure
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
// a daemon assisted hardware wallet. This interface serves the purpose of
// allowing LightningWallet to be seamlessly compatible with several wallets
// such as: uspv, btcwallet, Bitcoin Core, Electrum, etc. This interface then
// serves as a "base wallet", with Lightning Network awareness taking place at
// a "higher" level of abstraction. Essentially, an overlay wallet.
// Implementors of this interface must closely adhere to the documented
// behavior of all interface methods in order to ensure identical behavior
// across all concrete implementations.
type WalletController interface {
	// FetchInputInfo queries for the WalletController's knowledge of the
	// passed outpoint. If the base wallet determines this output is under
	// its control, then the original txout should be returned. Otherwise,
	// a non-nil error value of ErrNotMine should be returned instead.
	FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error)

	// ConfirmedBalance returns the sum of all the wallet's unspent outputs
	// that have at least confs confirmations. If confs is set to zero,
	// then all unspent outputs, including those currently in the mempool
	// will be included in the final sum.
	ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error)

	// NewAddress returns the next external or internal address for the
	// wallet dicatated by the value of the `change` paramter. If change is
	// true, then an internal address should be used, otherwise an external
	// address should be returned. The type of address returned is dictated
	// by the wallet's capabilities, and may be of type: p2sh, p2pkh,
	// p2wkh, p2wsh, etc.
	NewAddress(addrType AddressType, change bool) (btcutil.Address, error)

	// GetPrivKey retrives the underlying private key associated with the
	// passed address. If the wallet is unable to locate this private key
	// due to the address not being under control of the wallet, then an
	// error should be returned.
	GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error)

	// NewRawKey returns a raw private key controlled by the wallet. These
	// keys are used for the 2-of-2 multi-sig outputs for funding
	// transactions, as well as the pub key used for commitment transactions.
	NewRawKey() (*btcec.PublicKey, error)

	// FetchRootKey returns a root key which will be used by the
	// LightningWallet to deterministically generate secrets. The private
	// key returned by this method should remain constant in-between
	// WalletController restarts.
	FetchRootKey() (*btcec.PrivateKey, error)

	// SendOutputs funds, signs, and broadcasts a Bitcoin transaction
	// paying out to the specified outputs. In the case the wallet has
	// insufficient funds, or the outputs are non-standard, and error
	// should be returned.
	SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error)

	// ListUnspentWitness returns all unspent outputs which are version 0
	// witness programs. The 'confirms' parameter indicates the minimum
	// number of confirmations an output needs in order to be returned by
	// this method. Passing -1 as 'confirms' indicates that even
	// unconfirmed outputs should be returned.
	ListUnspentWitness(confirms int32) ([]*Utxo, error)

	// ListTransactionDetails returns a list of all transactions which are
	// relevant to the wallet.
	ListTransactionDetails() ([]*TransactionDetail, error)

	// LockOutpoint marks an outpoint as locked meaning it will no longer
	// be deemed as eligible for coin selection. Locking outputs are
	// utilized in order to avoid race conditions when selecting inputs for
	// usage when funding a channel.
	LockOutpoint(o wire.OutPoint)

	// UnlockOutpoint unlocks an previously locked output, marking it
	// eligible for coin seleciton.
	UnlockOutpoint(o wire.OutPoint)

	// PublishTransaction performs cursory validation (dust checks, etc),
	// then finally broadcasts the passed transaction to the Bitcoin network.
	PublishTransaction(tx *wire.MsgTx) error

	// Start initializes the wallet, making any neccessary connections,
	// starting up required goroutines etc.
	Start() error

	// Stop signals the wallet for shutdown. Shutdown may entail closing
	// any active sockets, database handles, stopping goroutines, etc.
	Stop() error
}

// BlockChainIO is a dedicated source which will be used to obtain queries
// related to the current state of the blockchain. The data returned by each of
// the defined methods within this interface should always return the most up
// to date data possible.
//
// TODO(roasbeef): move to diff package perhaps?
// TODO(roasbeef): move publish txn here?
type BlockChainIO interface {
	// GetCurrentHeight returns the current height of the valid most-work
	// chain the implementation is aware of.
	GetCurrentHeight() (int32, error)

	// GetTxOut returns the original output referenced by the passed
	// outpoint.
	GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error)

	// GetTransaction returns the full transaction identified by the passed
	// transaction ID.
	GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error)
}

// SignDescriptor houses the necessary information required to succesfully sign
// a given output. This struct is used by the Signer interface in order to gain
// access to critial data needed to generate a valid signature.
type SignDescriptor struct {
	// Pubkey is the public key to which the signature should be generated
	// over. The Signer should then generate a signature with the private
	// key corresponding to this public key.
	PubKey *btcec.PublicKey

	// RedeemScript is the full script required to properly redeem the
	// output. This field will only be populated if a p2wsh or a p2sh
	// output is being signed.
	RedeemScript []byte

	// Output is the target output which should be signed. The PkScript and
	// Value fields within the output should be properly populated,
	// otherwise an invalid signature may be generated.
	Output *wire.TxOut

	// HashType is the target sighash type that should be used when
	// generating the final sighash, and signature.
	HashType txscript.SigHashType

	// SigHashes is the pre-computed sighash midstate to be used when
	// generating the final sighash for signing.
	SigHashes *txscript.TxSigHashes

	// InputIndex is the target input within the transaction that should be
	// signed.
	InputIndex int
}

// Signer represents an abstract object capable of generating raw signatures as
// well as full complete input scripts given a valid SignDescriptor and
// transaction. This interface fully abstracts away signing paving the way for
// Signer implementations such as hardware wallets, hardware tokens, HSM's, or
// simply a regular wallet.
type Signer interface {
	// SignOutputRaw generates a signature for the passed transaction
	// according to the data within the passed SignDescriptor.
	//
	// NOTE: The resulting signature should be void of a sighash byte.
	SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error)

	// ComputeInputScript generates a complete InputIndex for the passed
	// transaction with the signature as defined within the passed
	// SignDescriptor. This method should be capable of generating the
	// proper input script for both regular p2wkh output and p2wkh outputs
	// nested within a regualr p2sh output.
	ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error)
}

// WalletDriver represents a "driver" for a particular concrete
// WalletController implementation. A driver is indentified by a globally
// unique string identifier along with a 'New()' method which is responsible
// for initializing a particular WalletController concrete implementation.
type WalletDriver struct {
	// WalletType is a string which uniquely identifes the WalletController
	// that this driver, drives.
	WalletType string

	// New creates a new instance of a concrete WalletController
	// implementation given a variadic set up arguments. The function takes
	// a varidaic number of interface paramters in order to provide
	// initialization flexibility, thereby accomodating several potential
	// WalletController implementations.
	New func(args ...interface{}) (WalletController, error)
}

var (
	wallets     = make(map[string]*WalletDriver)
	registerMtx sync.Mutex
)

// RegisteredWallets returns a slice of all currently registered notifiers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredWallets() []*WalletDriver {
	registerMtx.Lock()
	defer registerMtx.Unlock()

	registeredWallets := make([]*WalletDriver, 0, len(wallets))
	for _, wallet := range wallets {
		registeredWallets = append(registeredWallets, wallet)
	}

	return registeredWallets
}

// RegisterWallet registers a WalletDriver which is capable of driving a
// concrete WalletController interface. In the case that this driver has
// already been registered, an error is returned.
//
// NOTE: This function is safe for concurrent access.
func RegisterWallet(driver *WalletDriver) error {
	registerMtx.Lock()
	defer registerMtx.Unlock()

	if _, ok := wallets[driver.WalletType]; ok {
		return fmt.Errorf("wallet already registered")
	}

	wallets[driver.WalletType] = driver

	return nil
}

// SupportedWallets returns a slice of strings that represents the walelt
// drivers that have been registered and are therefore supported.
//
// NOTE: This function is safe for concurrent access.
func SupportedWallets() []string {
	registerMtx.Lock()
	defer registerMtx.Unlock()

	supportedWallets := make([]string, 0, len(wallets))
	for walletName := range wallets {
		supportedWallets = append(supportedWallets, walletName)
	}

	return supportedWallets
}
<commit_msg>lnwallet: extend the WalletController with a new txn pub/sub client<commit_after>package lnwallet

import (
	"errors"
	"fmt"
	"sync"

	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcd/txscript"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"
)

// ErrNotMine is an error denoting that a WalletController instance is unable
// to spend a specifid output.
var ErrNotMine = errors.New("the passed output doesn't belong to the wallet")

// AddressType is a enum-like type which denotes the possible address types
// WalletController supports.
type AddressType uint8

const (
	// WitnessPubKey represents a p2wkh address.
	WitnessPubKey AddressType = iota

	// NestedWitnessPubKey represents a p2sh output which is itself a
	// nested p2wkh output.
	NestedWitnessPubKey

	// PublicKey represents a regular p2pkh output.
	PubKeyHash
)

// Utxo is an unspent output denoted by its outpoint, and output value of the
// original output.
type Utxo struct {
	Value btcutil.Amount
	wire.OutPoint
}

// TransactionDetail describes a transaction with either inputs which belong to
// the wallet, or has outputs that pay to the wallet.
type TransactionDetail struct {
	// Hash is the transaction hash of the transaction.
	Hash wire.ShaHash

	// Value is the net value of this transaction (in satoshis) from the
	// PoV of the wallet. If this transaction purely spends from the
	// wallet's funds, then this value will be negative. Similarly, if this
	// transaction credits the wallet, then this value will be positive.
	Value btcutil.Amount

	// NumConfirmations is the number of confirmations this transaction
	// has. If the transaction is unconfirmed, then this value will be
	// zero.
	NumConfirmations int32

	// BlockHeight is the hash of the block which includes this
	// transaction. Unconfirmed transactions will have a nil value for this
	// field.
	BlockHash *wire.ShaHash

	// BlockHeight is the height of the block including this transaction.
	// Unconfirmed transaction will show a height of zero.
	BlockHeight int32

	// Timestamp is the unix timestamp of the block including this
	// transaction. If the transaction is unconfirmed, then this will be a
	// timestamp of txn creation.
	Timestamp int64

	// TotalFees is the total fee in satoshis paid by this transaction.
	TotalFees int64
}

// TransactionSubscription is an interface which describes an object capable of
// receiving notifications of new transaction related to the underlying wallet.
// TODO(roasbeef): add balance updates?
type TransactionSubscription interface {
	// ConfirmedTransactions returns a channel which will be sent on as new
	// relevant transactions are confirmed.
	ConfirmedTransactions() chan *TransactionDetail

	// UnconfirmedTransactions returns a channel which will be sent on as
	// new relevant transactions are seen within the network.
	UnconfirmedTransactions() chan *TransactionDetail

	// Cancel finalizes the subscription, cleaning up any resources
	// allocated.
	Cancel()
}

// WalletController defines an abstract interface for controlling a local Pure
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
// a daemon assisted hardware wallet. This interface serves the purpose of
// allowing LightningWallet to be seamlessly compatible with several wallets
// such as: uspv, btcwallet, Bitcoin Core, Electrum, etc. This interface then
// serves as a "base wallet", with Lightning Network awareness taking place at
// a "higher" level of abstraction. Essentially, an overlay wallet.
// Implementors of this interface must closely adhere to the documented
// behavior of all interface methods in order to ensure identical behavior
// across all concrete implementations.
type WalletController interface {
	// FetchInputInfo queries for the WalletController's knowledge of the
	// passed outpoint. If the base wallet determines this output is under
	// its control, then the original txout should be returned. Otherwise,
	// a non-nil error value of ErrNotMine should be returned instead.
	FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error)

	// ConfirmedBalance returns the sum of all the wallet's unspent outputs
	// that have at least confs confirmations. If confs is set to zero,
	// then all unspent outputs, including those currently in the mempool
	// will be included in the final sum.
	ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error)

	// NewAddress returns the next external or internal address for the
	// wallet dicatated by the value of the `change` paramter. If change is
	// true, then an internal address should be used, otherwise an external
	// address should be returned. The type of address returned is dictated
	// by the wallet's capabilities, and may be of type: p2sh, p2pkh,
	// p2wkh, p2wsh, etc.
	NewAddress(addrType AddressType, change bool) (btcutil.Address, error)

	// GetPrivKey retrives the underlying private key associated with the
	// passed address. If the wallet is unable to locate this private key
	// due to the address not being under control of the wallet, then an
	// error should be returned.
	GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error)

	// NewRawKey returns a raw private key controlled by the wallet. These
	// keys are used for the 2-of-2 multi-sig outputs for funding
	// transactions, as well as the pub key used for commitment transactions.
	NewRawKey() (*btcec.PublicKey, error)

	// FetchRootKey returns a root key which will be used by the
	// LightningWallet to deterministically generate secrets. The private
	// key returned by this method should remain constant in-between
	// WalletController restarts.
	FetchRootKey() (*btcec.PrivateKey, error)

	// SendOutputs funds, signs, and broadcasts a Bitcoin transaction
	// paying out to the specified outputs. In the case the wallet has
	// insufficient funds, or the outputs are non-standard, and error
	// should be returned.
	SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error)

	// ListUnspentWitness returns all unspent outputs which are version 0
	// witness programs. The 'confirms' parameter indicates the minimum
	// number of confirmations an output needs in order to be returned by
	// this method. Passing -1 as 'confirms' indicates that even
	// unconfirmed outputs should be returned.
	ListUnspentWitness(confirms int32) ([]*Utxo, error)

	// ListTransactionDetails returns a list of all transactions which are
	// relevant to the wallet.
	ListTransactionDetails() ([]*TransactionDetail, error)

	// LockOutpoint marks an outpoint as locked meaning it will no longer
	// be deemed as eligible for coin selection. Locking outputs are
	// utilized in order to avoid race conditions when selecting inputs for
	// usage when funding a channel.
	LockOutpoint(o wire.OutPoint)

	// UnlockOutpoint unlocks an previously locked output, marking it
	// eligible for coin seleciton.
	UnlockOutpoint(o wire.OutPoint)

	// PublishTransaction performs cursory validation (dust checks, etc),
	// then finally broadcasts the passed transaction to the Bitcoin network.
	PublishTransaction(tx *wire.MsgTx) error

	// SubscribeTransactions returns a TransactionSubscription client which
	// is capable of receiving async notifications as new transactions
	// related to the wallet are seen within the network, or found in
	// blocks.
	//
	// NOTE: a non-nil error shuold be returned if notifications aren't
	// supported.
	//
	// TODO(roasbeef): make distinct interface?
	SubscribeTransactions() (TransactionSubscription, error)

	// Start initializes the wallet, making any neccessary connections,
	// starting up required goroutines etc.
	Start() error

	// Stop signals the wallet for shutdown. Shutdown may entail closing
	// any active sockets, database handles, stopping goroutines, etc.
	Stop() error
}

// BlockChainIO is a dedicated source which will be used to obtain queries
// related to the current state of the blockchain. The data returned by each of
// the defined methods within this interface should always return the most up
// to date data possible.
//
// TODO(roasbeef): move to diff package perhaps?
// TODO(roasbeef): move publish txn here?
type BlockChainIO interface {
	// GetCurrentHeight returns the current height of the valid most-work
	// chain the implementation is aware of.
	GetCurrentHeight() (int32, error)

	// GetTxOut returns the original output referenced by the passed
	// outpoint.
	GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error)

	// GetTransaction returns the full transaction identified by the passed
	// transaction ID.
	GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error)
}

// SignDescriptor houses the necessary information required to succesfully sign
// a given output. This struct is used by the Signer interface in order to gain
// access to critial data needed to generate a valid signature.
type SignDescriptor struct {
	// Pubkey is the public key to which the signature should be generated
	// over. The Signer should then generate a signature with the private
	// key corresponding to this public key.
	PubKey *btcec.PublicKey

	// RedeemScript is the full script required to properly redeem the
	// output. This field will only be populated if a p2wsh or a p2sh
	// output is being signed.
	RedeemScript []byte

	// Output is the target output which should be signed. The PkScript and
	// Value fields within the output should be properly populated,
	// otherwise an invalid signature may be generated.
	Output *wire.TxOut

	// HashType is the target sighash type that should be used when
	// generating the final sighash, and signature.
	HashType txscript.SigHashType

	// SigHashes is the pre-computed sighash midstate to be used when
	// generating the final sighash for signing.
	SigHashes *txscript.TxSigHashes

	// InputIndex is the target input within the transaction that should be
	// signed.
	InputIndex int
}

// Signer represents an abstract object capable of generating raw signatures as
// well as full complete input scripts given a valid SignDescriptor and
// transaction. This interface fully abstracts away signing paving the way for
// Signer implementations such as hardware wallets, hardware tokens, HSM's, or
// simply a regular wallet.
type Signer interface {
	// SignOutputRaw generates a signature for the passed transaction
	// according to the data within the passed SignDescriptor.
	//
	// NOTE: The resulting signature should be void of a sighash byte.
	SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error)

	// ComputeInputScript generates a complete InputIndex for the passed
	// transaction with the signature as defined within the passed
	// SignDescriptor. This method should be capable of generating the
	// proper input script for both regular p2wkh output and p2wkh outputs
	// nested within a regualr p2sh output.
	ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error)
}

// WalletDriver represents a "driver" for a particular concrete
// WalletController implementation. A driver is indentified by a globally
// unique string identifier along with a 'New()' method which is responsible
// for initializing a particular WalletController concrete implementation.
type WalletDriver struct {
	// WalletType is a string which uniquely identifes the WalletController
	// that this driver, drives.
	WalletType string

	// New creates a new instance of a concrete WalletController
	// implementation given a variadic set up arguments. The function takes
	// a varidaic number of interface paramters in order to provide
	// initialization flexibility, thereby accomodating several potential
	// WalletController implementations.
	New func(args ...interface{}) (WalletController, error)
}

var (
	wallets     = make(map[string]*WalletDriver)
	registerMtx sync.Mutex
)

// RegisteredWallets returns a slice of all currently registered notifiers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredWallets() []*WalletDriver {
	registerMtx.Lock()
	defer registerMtx.Unlock()

	registeredWallets := make([]*WalletDriver, 0, len(wallets))
	for _, wallet := range wallets {
		registeredWallets = append(registeredWallets, wallet)
	}

	return registeredWallets
}

// RegisterWallet registers a WalletDriver which is capable of driving a
// concrete WalletController interface. In the case that this driver has
// already been registered, an error is returned.
//
// NOTE: This function is safe for concurrent access.
func RegisterWallet(driver *WalletDriver) error {
	registerMtx.Lock()
	defer registerMtx.Unlock()

	if _, ok := wallets[driver.WalletType]; ok {
		return fmt.Errorf("wallet already registered")
	}

	wallets[driver.WalletType] = driver

	return nil
}

// SupportedWallets returns a slice of strings that represents the walelt
// drivers that have been registered and are therefore supported.
//
// NOTE: This function is safe for concurrent access.
func SupportedWallets() []string {
	registerMtx.Lock()
	defer registerMtx.Unlock()

	supportedWallets := make([]string, 0, len(wallets))
	for walletName := range wallets {
		supportedWallets = append(supportedWallets, walletName)
	}

	return supportedWallets
}
<|endoftext|>


<commit_before>package logfmt

import (
	"bytes"
	"errors"
	"testing"
	"time"

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

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

	buf := Buffer{}
	tm := time.Unix(1234567890, 987654321).UTC()
	buf.WriteTimestamp(tm)
	buf.WriteKey("info")
	buf.WriteProperty("key", "value")

	bytes := bytes.Buffer{}
	buf.WriteTo(&bytes)
	text := string(bytes.Bytes())
	assert.Equal("2009-02-13T23:31:30.987654+0000 info key=value", text)
}

func TestFormatting(t *testing.T) {
	assert := assert.New(t)
	testCases := []struct {
		Value    interface{}
		Expected string
	}{
		{Value: true, Expected: "key=true"},
		{Value: false, Expected: "key=false"},
		{Value: byte(0x10), Expected: "key=16"},
		{Value: complex(float32(10), float32(11)), Expected: "key=(10+11i)"},
		{Value: complex(float64(10.4), float64(11.5)), Expected: "key=(10.4+11.5i)"},
		{Value: errors.New("This is an error"), Expected: `key="This is an error"`},
		{Value: float32(3.14159), Expected: "key=3.14159"},
		{Value: float64(31.4159), Expected: "key=31.4159"},
		{Value: int(1), Expected: "key=1"},
	}

	for _, tc := range testCases {
		buf := Buffer{}
		buf.WriteProperty("key", tc.Value)
		assert.Equal(tc.Expected, buf.String())
	}
}
<commit_msg>added test cases for logfmt<commit_after>package logfmt

import (
	"bytes"
	"errors"
	"fmt"
	"testing"
	"time"

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

type stringer int

func (s stringer) String() string {
	return fmt.Sprintf("stringer: %d", s)
}

type textMarshaler int

func (tm textMarshaler) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprintf("textMarshaler: %d", tm)), nil
}

type needsSprintf struct {
	A int
	B string
}

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

	buf := Buffer{}
	tm := time.Unix(1234567890, 987654321).UTC()
	buf.WriteTimestamp(tm)
	buf.WriteKey("info")
	buf.WriteProperty("key", "value")

	bytes := bytes.Buffer{}
	buf.WriteTo(&bytes)
	text := string(bytes.Bytes())
	assert.Equal("2009-02-13T23:31:30.987654+0000 info key=value", text)
}

func TestFormatting(t *testing.T) {
	assert := assert.New(t)
	testCases := []struct {
		Value    interface{}
		Expected string
	}{
		{Value: true, Expected: "key=true"},
		{Value: false, Expected: "key=false"},
		{Value: byte(0x10), Expected: "key=16"},
		{Value: complex(float32(10), float32(11)), Expected: "key=(10+11i)"},
		{Value: complex(float64(10.4), float64(11.5)), Expected: "key=(10.4+11.5i)"},
		{Value: errors.New("This is an error"), Expected: `key="This is an error"`},
		{Value: float32(3.14159), Expected: "key=3.14159"},
		{Value: float64(31.4159), Expected: "key=31.4159"},
		{Value: int(1), Expected: "key=1"},
		{Value: int16(2), Expected: "key=2"},
		{Value: int32(3), Expected: "key=3"},
		{Value: int64(4), Expected: "key=4"},
		{Value: int8(5), Expected: "key=5"},
		{Value: "string", Expected: `key=string`},
		{Value: uint(1), Expected: "key=1"},
		{Value: uint16(2), Expected: "key=2"},
		{Value: uint32(3), Expected: "key=3"},
		{Value: uint64(4), Expected: "key=4"},
		{Value: uintptr(3041255), Expected: "key=3041255"},
		{Value: stringer(44), Expected: `key="stringer: 44"`},
		{Value: textMarshaler(45), Expected: `key="textMarshaler: 45"`},
		{Value: needsSprintf{46, "text value"}, Expected: `key="{46 text value}"`},
	}

	for _, tc := range testCases {
		buf := Buffer{}
		buf.WriteProperty("key", tc.Value)
		assert.Equal(tc.Expected, buf.String())
	}
}
<|endoftext|>


<commit_before>package logging

import (
	"encoding/json"
	"io"
	"os"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

// AccessLogger is a middleware for logging access info
func AccessLogger(out io.Writer) gin.HandlerFunc {

	if out == nil {
		out = os.Stdout
	}

	m := &sync.Mutex{}

	return func(c *gin.Context) {

		start := time.Now()

		c.Next()

		go func(ctx *gin.Context) {

			al := accessLog{
				logInfo: generateLogInfo(ctx, start),
			}

			if err := ctx.LastError(); err != nil {
				al.Error = err
			}

			bytes, err := json.Marshal(al)
			if err != nil {
				panic(err)
			}

			bytes = append(bytes, 10)
			m.Lock()
			defer m.Unlock()
			out.Write(bytes)
		}(c.Copy())
	}
}

// ActivityLogger is a middleware for logging user action info
func ActivityLogger(out io.Writer, getExtra func(c *gin.Context) (interface{}, error)) gin.HandlerFunc {

	if out == nil {
		out = os.Stdout
	}

	m := &sync.Mutex{}

	return func(c *gin.Context) {

		// check a request method
		if c.Request.Method == "GET" {
			return
		}

		start := time.Now()
		b, err := convertToMapFromBody(c)
		if err != nil {
			panic(err)
		}

		c.Next()

		// check a response status
		if c.Writer.Status() < 200 || c.Writer.Status() > 299 {
			return
		}

		go func(ctx *gin.Context) {

			al := activityLog{
				logInfo:     generateLogInfo(ctx, start),
				RequestBody: b,
			}

			// get to Extra
			if getExtra != nil {
				al.Extra, err = getExtra(ctx)
				if err != nil {
					panic(err)
				}
			}

			bytes, err := json.Marshal(al)
			if err != nil {
				panic(err)
			}

			bytes = append(bytes, 10)
			m.Lock()
			defer m.Unlock()
			out.Write(bytes)
		}(c.Copy())
	}
}
<commit_msg>Add RecoverLoggingFailure function<commit_after>package logging

import (
	"encoding/json"
	"io"
	"os"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

// RecoverLoggingFailure is a recover when failed to logging
var RecoverLoggingFailure func()

// AccessLogger is a middleware for logging access info
func AccessLogger(out io.Writer) gin.HandlerFunc {

	if out == nil {
		out = os.Stdout
	}

	m := &sync.Mutex{}

	return func(c *gin.Context) {

		if RecoverLoggingFailure != nil {
			defer RecoverLoggingFailure()
		}

		start := time.Now()

		c.Next()

		al := accessLog{
			logInfo: generateLogInfo(c, start),
		}

		if err := c.LastError(); err != nil {
			al.Error = err
		}

		bytes, err := json.Marshal(al)
		if err != nil {
			panic(err)
		}

		bytes = append(bytes, 10)

		// Async write
		go func() {
			m.Lock()
			defer m.Unlock()
			out.Write(bytes)
		}()
	}
}

// ActivityLogger is a middleware for logging user action info
func ActivityLogger(out io.Writer, getExtra func(c *gin.Context) (interface{}, error)) gin.HandlerFunc {

	if out == nil {
		out = os.Stdout
	}

	m := &sync.Mutex{}

	return func(c *gin.Context) {

		if RecoverLoggingFailure != nil {
			defer RecoverLoggingFailure()
		}

		// check a request method
		if c.Request.Method == "GET" {
			return
		}

		start := time.Now()
		b, err := convertToMapFromBody(c)
		if err != nil {
			panic(err)
		}

		c.Next()

		// check a response status
		if c.Writer.Status() < 200 || c.Writer.Status() > 299 {
			return
		}

		al := activityLog{
			logInfo:     generateLogInfo(c, start),
			RequestBody: b,
		}

		// get to Extra
		if getExtra != nil {
			al.Extra, err = getExtra(c)
			if err != nil {
				panic(err)
			}
		}

		bytes, err := json.Marshal(al)
		if err != nil {
			panic(err)
		}

		bytes = append(bytes, 10)

		// Async write
		go func() {
			m.Lock()
			defer m.Unlock()
			out.Write(bytes)
		}()
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The cert-manager 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 middleware

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"golang.org/x/crypto/acme"

	"github.com/cert-manager/cert-manager/pkg/acme/client"
	logf "github.com/cert-manager/cert-manager/pkg/logs"
)

const (
	timeout = time.Second * 10
)

func NewLogger(baseCl client.Interface) client.Interface {
	return &Logger{
		baseCl: baseCl,
		log:    logf.Log.WithName("acme-middleware"),
	}
}

// Logger is a glog based logging middleware for an ACME client
type Logger struct {
	baseCl client.Interface
	log    logr.Logger
}

var _ client.Interface = &Logger{}

func (l *Logger) AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) {
	l.log.V(logf.TraceLevel).Info("Calling AuthorizeOrder")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.AuthorizeOrder(ctx, id, opt...)
}

func (l *Logger) GetOrder(ctx context.Context, url string) (*acme.Order, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetOrder")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.GetOrder(ctx, url)
}

func (l *Logger) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
	l.log.V(logf.TraceLevel).Info("Calling FetchCert")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.FetchCert(ctx, url, bundle)
}

func (l *Logger) ListCertAlternates(ctx context.Context, url string) ([]string, error) {
	l.log.V(logf.TraceLevel).Info("Calling ListCertAlternates")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.ListCertAlternates(ctx, url)
}

func (l *Logger) WaitOrder(ctx context.Context, url string) (*acme.Order, error) {
	l.log.V(logf.TraceLevel).Info("Calling WaitOrder")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.WaitOrder(ctx, url)
}

func (l *Logger) CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
	l.log.V(logf.TraceLevel).Info("Calling CreateOrderCert")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.CreateOrderCert(ctx, finalizeURL, csr, bundle)
}

func (l *Logger) Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) {
	l.log.V(logf.TraceLevel).Info("Calling Accept")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.Accept(ctx, chal)
}

func (l *Logger) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetChallenge")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.GetChallenge(ctx, url)
}

func (l *Logger) GetAuthorization(ctx context.Context, url string) (*acme.Authorization, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetAuthorization")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.GetAuthorization(ctx, url)
}

func (l *Logger) WaitAuthorization(ctx context.Context, url string) (*acme.Authorization, error) {
	l.log.V(logf.TraceLevel).Info("Calling WaitAuthorization")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.WaitAuthorization(ctx, url)
}

func (l *Logger) Register(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) {
	l.log.V(logf.TraceLevel).Info("Calling Register")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.Register(ctx, a, prompt)
}

func (l *Logger) GetReg(ctx context.Context, url string) (*acme.Account, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetReg")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.GetReg(ctx, url)
}

func (l *Logger) HTTP01ChallengeResponse(token string) (string, error) {
	l.log.V(logf.TraceLevel).Info("Calling HTTP01ChallengeResponse")
	return l.baseCl.HTTP01ChallengeResponse(token)
}

func (l *Logger) DNS01ChallengeRecord(token string) (string, error) {
	l.log.V(logf.TraceLevel).Info("Calling DNS01ChallengeRecord")
	return l.baseCl.DNS01ChallengeRecord(token)
}

func (l *Logger) Discover(ctx context.Context) (acme.Directory, error) {
	l.log.V(logf.TraceLevel).Info("Calling Discover")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.Discover(ctx)
}

func (l *Logger) UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) {
	l.log.V(logf.TraceLevel).Info("Calling UpdateReg")

	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	return l.baseCl.UpdateReg(ctx, a)
}
<commit_msg>Remove timeouts in ACME logging middleware<commit_after>/*
Copyright 2020 The cert-manager 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 middleware

import (
	"context"

	"github.com/go-logr/logr"
	"golang.org/x/crypto/acme"

	"github.com/cert-manager/cert-manager/pkg/acme/client"
	logf "github.com/cert-manager/cert-manager/pkg/logs"
)

func NewLogger(baseCl client.Interface) client.Interface {
	return &Logger{
		baseCl: baseCl,
		log:    logf.Log.WithName("acme-middleware"),
	}
}

// Logger is a glog based logging middleware for an ACME client
type Logger struct {
	baseCl client.Interface
	log    logr.Logger
}

var _ client.Interface = &Logger{}

func (l *Logger) AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) {
	l.log.V(logf.TraceLevel).Info("Calling AuthorizeOrder")

	return l.baseCl.AuthorizeOrder(ctx, id, opt...)
}

func (l *Logger) GetOrder(ctx context.Context, url string) (*acme.Order, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetOrder")

	return l.baseCl.GetOrder(ctx, url)
}

func (l *Logger) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
	l.log.V(logf.TraceLevel).Info("Calling FetchCert")

	return l.baseCl.FetchCert(ctx, url, bundle)
}

func (l *Logger) ListCertAlternates(ctx context.Context, url string) ([]string, error) {
	l.log.V(logf.TraceLevel).Info("Calling ListCertAlternates")

	return l.baseCl.ListCertAlternates(ctx, url)
}

func (l *Logger) WaitOrder(ctx context.Context, url string) (*acme.Order, error) {
	l.log.V(logf.TraceLevel).Info("Calling WaitOrder")

	return l.baseCl.WaitOrder(ctx, url)
}

func (l *Logger) CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
	l.log.V(logf.TraceLevel).Info("Calling CreateOrderCert")

	return l.baseCl.CreateOrderCert(ctx, finalizeURL, csr, bundle)
}

func (l *Logger) Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) {
	l.log.V(logf.TraceLevel).Info("Calling Accept")

	return l.baseCl.Accept(ctx, chal)
}

func (l *Logger) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetChallenge")

	return l.baseCl.GetChallenge(ctx, url)
}

func (l *Logger) GetAuthorization(ctx context.Context, url string) (*acme.Authorization, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetAuthorization")

	return l.baseCl.GetAuthorization(ctx, url)
}

func (l *Logger) WaitAuthorization(ctx context.Context, url string) (*acme.Authorization, error) {
	l.log.V(logf.TraceLevel).Info("Calling WaitAuthorization")

	return l.baseCl.WaitAuthorization(ctx, url)
}

func (l *Logger) Register(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) {
	l.log.V(logf.TraceLevel).Info("Calling Register")

	return l.baseCl.Register(ctx, a, prompt)
}

func (l *Logger) GetReg(ctx context.Context, url string) (*acme.Account, error) {
	l.log.V(logf.TraceLevel).Info("Calling GetReg")

	return l.baseCl.GetReg(ctx, url)
}

func (l *Logger) HTTP01ChallengeResponse(token string) (string, error) {
	l.log.V(logf.TraceLevel).Info("Calling HTTP01ChallengeResponse")

	return l.baseCl.HTTP01ChallengeResponse(token)
}

func (l *Logger) DNS01ChallengeRecord(token string) (string, error) {
	l.log.V(logf.TraceLevel).Info("Calling DNS01ChallengeRecord")

	return l.baseCl.DNS01ChallengeRecord(token)
}

func (l *Logger) Discover(ctx context.Context) (acme.Directory, error) {
	l.log.V(logf.TraceLevel).Info("Calling Discover")

	return l.baseCl.Discover(ctx)
}

func (l *Logger) UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) {
	l.log.V(logf.TraceLevel).Info("Calling UpdateReg")

	return l.baseCl.UpdateReg(ctx, a)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 testing

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/spf13/pflag"

	"k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/registry/generic/registry"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	"k8s.io/client-go/kubernetes"
	restclient "k8s.io/client-go/rest"
)

// TearDownFunc is to be called to tear down a test server.
type TearDownFunc func()

// TestServerInstanceOptions Instance options the TestServer
type TestServerInstanceOptions struct {
	// DisableStorageCleanup Disable the automatic storage cleanup
	DisableStorageCleanup bool
}

// TestServer return values supplied by kube-test-ApiServer
type TestServer struct {
	ClientConfig *restclient.Config                              // Rest client config
	ServerOpts   *options.CustomResourceDefinitionsServerOptions // ServerOpts
	TearDownFn   TearDownFunc                                    // TearDown function
	TmpDir       string                                          // Temp Dir used, by the apiserver
}

// Logger allows t.Testing and b.Testing to be passed to StartTestServer and StartTestServerOrDie
type Logger interface {
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
	Logf(format string, args ...interface{})
}

// NewDefaultTestServerOptions Default options for TestServer instances
func NewDefaultTestServerOptions() *TestServerInstanceOptions {
	return &TestServerInstanceOptions{
		DisableStorageCleanup: false,
	}
}

// StartTestServer starts a apiextensions-apiserver. A rest client config and a tear-down func,
// and location of the tmpdir are returned.
//
// Note: we return a tear-down func instead of a stop channel because the later will leak temporary
// 		 files that because Golang testing's call to os.Exit will not give a stop channel go routine
// 		 enough time to remove temporary files.
func StartTestServer(t Logger, instanceOptions *TestServerInstanceOptions, customFlags []string, storageConfig *storagebackend.Config) (result TestServer, err error) {
	if instanceOptions == nil {
		instanceOptions = NewDefaultTestServerOptions()
	}

	// TODO : Remove TrackStorageCleanup below when PR
	// https://github.com/kubernetes/kubernetes/pull/50690
	// merges as that shuts down storage properly
	if !instanceOptions.DisableStorageCleanup {
		registry.TrackStorageCleanup()
	}

	stopCh := make(chan struct{})
	tearDown := func() {
		if !instanceOptions.DisableStorageCleanup {
			registry.CleanupStorage()
		}
		close(stopCh)
		if len(result.TmpDir) != 0 {
			os.RemoveAll(result.TmpDir)
		}
	}
	defer func() {
		if result.TearDownFn == nil {
			tearDown()
		}
	}()

	result.TmpDir, err = ioutil.TempDir("", "apiextensions-apiserver")
	if err != nil {
		return result, fmt.Errorf("failed to create temp dir: %v", err)
	}

	fs := pflag.NewFlagSet("test", pflag.PanicOnError)

	s := options.NewCustomResourceDefinitionsServerOptions(os.Stdout, os.Stderr)
	s.AddFlags(fs)

	s.RecommendedOptions.SecureServing.Listener, s.RecommendedOptions.SecureServing.BindPort, err = createLocalhostListenerOnFreePort()
	if err != nil {
		return result, fmt.Errorf("failed to create listener: %v", err)
	}
	s.RecommendedOptions.SecureServing.ServerCert.CertDirectory = result.TmpDir
	s.RecommendedOptions.SecureServing.ExternalAddress = s.RecommendedOptions.SecureServing.Listener.Addr().(*net.TCPAddr).IP // use listener addr although it is a loopback device

	pkgPath, err := pkgPath(t)
	if err != nil {
		return result, err
	}
	s.RecommendedOptions.SecureServing.ServerCert.FixtureDirectory = filepath.Join(pkgPath, "testdata")

	if storageConfig != nil {
		s.RecommendedOptions.Etcd.StorageConfig = *storageConfig
	}
	s.APIEnablement.RuntimeConfig.Set("api/all=true")

	fs.Parse(customFlags)

	if err := s.Complete(); err != nil {
		return result, fmt.Errorf("failed to set default options: %v", err)
	}
	if err := s.Validate(); err != nil {
		return result, fmt.Errorf("failed to validate options: %v", err)
	}

	t.Logf("runtime-config=%v", s.APIEnablement.RuntimeConfig)
	t.Logf("Starting apiextensions-apiserver on port %d...", s.RecommendedOptions.SecureServing.BindPort)

	config, err := s.Config()
	if err != nil {
		return result, fmt.Errorf("failed to create config from options: %v", err)
	}
	server, err := config.Complete().New(genericapiserver.NewEmptyDelegate())
	if err != nil {
		return result, fmt.Errorf("failed to create server: %v", err)
	}

	errCh := make(chan error)
	go func(stopCh <-chan struct{}) {
		if err := server.GenericAPIServer.PrepareRun().Run(stopCh); err != nil {
			errCh <- err
		}
	}(stopCh)

	t.Logf("Waiting for /healthz to be ok...")

	client, err := kubernetes.NewForConfig(server.GenericAPIServer.LoopbackClientConfig)
	if err != nil {
		return result, fmt.Errorf("failed to create a client: %v", err)
	}
	err = wait.Poll(100*time.Millisecond, time.Minute, func() (bool, error) {
		select {
		case err := <-errCh:
			return false, err
		default:
		}

		result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(context.TODO())
		status := 0
		result.StatusCode(&status)
		if status == 200 {
			return true, nil
		}
		return false, nil
	})
	if err != nil {
		return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err)
	}

	// from here the caller must call tearDown
	result.ClientConfig = server.GenericAPIServer.LoopbackClientConfig
	result.ServerOpts = s
	result.TearDownFn = tearDown

	return result, nil
}

// StartTestServerOrDie calls StartTestServer t.Fatal if it does not succeed.
func StartTestServerOrDie(t Logger, instanceOptions *TestServerInstanceOptions, flags []string, storageConfig *storagebackend.Config) *TestServer {
	result, err := StartTestServer(t, instanceOptions, flags, storageConfig)
	if err == nil {
		return &result
	}

	t.Fatalf("failed to launch server: %v", err)
	return nil
}

func createLocalhostListenerOnFreePort() (net.Listener, int, error) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return nil, 0, err
	}

	// get port
	tcpAddr, ok := ln.Addr().(*net.TCPAddr)
	if !ok {
		ln.Close()
		return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
	}

	return ln, tcpAddr.Port, nil
}

// pkgPath returns the absolute file path to this package's directory. With go
// test, we can just look at the runtime call stack. However, bazel compiles go
// binaries with the -trimpath option so the simple approach fails however we
// can consult environment variables to derive the path.
//
// The approach taken here works for both go test and bazel on the assumption
// that if and only if trimpath is passed, we are running under bazel.
func pkgPath(t Logger) (string, error) {
	_, thisFile, _, ok := runtime.Caller(0)
	if !ok {
		return "", fmt.Errorf("failed to get current file")
	}

	pkgPath := filepath.Dir(thisFile)

	// If we find bazel env variables, then -trimpath was passed so we need to
	// construct the path from the environment.
	if testSrcdir, testWorkspace := os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE"); testSrcdir != "" && testWorkspace != "" {
		t.Logf("Detected bazel env varaiables: TEST_SRCDIR=%q TEST_WORKSPACE=%q", testSrcdir, testWorkspace)
		pkgPath = filepath.Join(testSrcdir, testWorkspace, pkgPath)
	}

	// If the path is still not absolute, something other than bazel compiled
	// with -trimpath.
	if !filepath.IsAbs(pkgPath) {
		return "", fmt.Errorf("can't construct an absolute path from %q", pkgPath)
	}

	t.Logf("Resolved testserver package path to: %q", pkgPath)

	return pkgPath, nil
}
<commit_msg>Cleanup no-longer used storage cleanup method<commit_after>/*
Copyright 2018 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 testing

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/spf13/pflag"

	"k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
	"k8s.io/apimachinery/pkg/util/wait"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	"k8s.io/client-go/kubernetes"
	restclient "k8s.io/client-go/rest"
)

// TearDownFunc is to be called to tear down a test server.
type TearDownFunc func()

// TestServerInstanceOptions Instance options the TestServer
type TestServerInstanceOptions struct {
}

// TestServer return values supplied by kube-test-ApiServer
type TestServer struct {
	ClientConfig *restclient.Config                              // Rest client config
	ServerOpts   *options.CustomResourceDefinitionsServerOptions // ServerOpts
	TearDownFn   TearDownFunc                                    // TearDown function
	TmpDir       string                                          // Temp Dir used, by the apiserver
}

// Logger allows t.Testing and b.Testing to be passed to StartTestServer and StartTestServerOrDie
type Logger interface {
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
	Logf(format string, args ...interface{})
}

// NewDefaultTestServerOptions Default options for TestServer instances
func NewDefaultTestServerOptions() *TestServerInstanceOptions {
	return &TestServerInstanceOptions{}
}

// StartTestServer starts a apiextensions-apiserver. A rest client config and a tear-down func,
// and location of the tmpdir are returned.
//
// Note: we return a tear-down func instead of a stop channel because the later will leak temporary
// 		 files that because Golang testing's call to os.Exit will not give a stop channel go routine
// 		 enough time to remove temporary files.
func StartTestServer(t Logger, _ *TestServerInstanceOptions, customFlags []string, storageConfig *storagebackend.Config) (result TestServer, err error) {
	stopCh := make(chan struct{})
	tearDown := func() {
		// Closing stopCh is stopping apiextensions apiserver and its
		// delegates, which itself is cleaning up after itself,
		// including shutting down its storage layer.
		close(stopCh)
		if len(result.TmpDir) != 0 {
			os.RemoveAll(result.TmpDir)
		}
	}
	defer func() {
		if result.TearDownFn == nil {
			tearDown()
		}
	}()

	result.TmpDir, err = ioutil.TempDir("", "apiextensions-apiserver")
	if err != nil {
		return result, fmt.Errorf("failed to create temp dir: %v", err)
	}

	fs := pflag.NewFlagSet("test", pflag.PanicOnError)

	s := options.NewCustomResourceDefinitionsServerOptions(os.Stdout, os.Stderr)
	s.AddFlags(fs)

	s.RecommendedOptions.SecureServing.Listener, s.RecommendedOptions.SecureServing.BindPort, err = createLocalhostListenerOnFreePort()
	if err != nil {
		return result, fmt.Errorf("failed to create listener: %v", err)
	}
	s.RecommendedOptions.SecureServing.ServerCert.CertDirectory = result.TmpDir
	s.RecommendedOptions.SecureServing.ExternalAddress = s.RecommendedOptions.SecureServing.Listener.Addr().(*net.TCPAddr).IP // use listener addr although it is a loopback device

	pkgPath, err := pkgPath(t)
	if err != nil {
		return result, err
	}
	s.RecommendedOptions.SecureServing.ServerCert.FixtureDirectory = filepath.Join(pkgPath, "testdata")

	if storageConfig != nil {
		s.RecommendedOptions.Etcd.StorageConfig = *storageConfig
	}
	s.APIEnablement.RuntimeConfig.Set("api/all=true")

	fs.Parse(customFlags)

	if err := s.Complete(); err != nil {
		return result, fmt.Errorf("failed to set default options: %v", err)
	}
	if err := s.Validate(); err != nil {
		return result, fmt.Errorf("failed to validate options: %v", err)
	}

	t.Logf("runtime-config=%v", s.APIEnablement.RuntimeConfig)
	t.Logf("Starting apiextensions-apiserver on port %d...", s.RecommendedOptions.SecureServing.BindPort)

	config, err := s.Config()
	if err != nil {
		return result, fmt.Errorf("failed to create config from options: %v", err)
	}
	server, err := config.Complete().New(genericapiserver.NewEmptyDelegate())
	if err != nil {
		return result, fmt.Errorf("failed to create server: %v", err)
	}

	errCh := make(chan error)
	go func(stopCh <-chan struct{}) {
		if err := server.GenericAPIServer.PrepareRun().Run(stopCh); err != nil {
			errCh <- err
		}
	}(stopCh)

	t.Logf("Waiting for /healthz to be ok...")

	client, err := kubernetes.NewForConfig(server.GenericAPIServer.LoopbackClientConfig)
	if err != nil {
		return result, fmt.Errorf("failed to create a client: %v", err)
	}
	err = wait.Poll(100*time.Millisecond, time.Minute, func() (bool, error) {
		select {
		case err := <-errCh:
			return false, err
		default:
		}

		result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(context.TODO())
		status := 0
		result.StatusCode(&status)
		if status == 200 {
			return true, nil
		}
		return false, nil
	})
	if err != nil {
		return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err)
	}

	// from here the caller must call tearDown
	result.ClientConfig = server.GenericAPIServer.LoopbackClientConfig
	result.ServerOpts = s
	result.TearDownFn = tearDown

	return result, nil
}

// StartTestServerOrDie calls StartTestServer t.Fatal if it does not succeed.
func StartTestServerOrDie(t Logger, instanceOptions *TestServerInstanceOptions, flags []string, storageConfig *storagebackend.Config) *TestServer {
	result, err := StartTestServer(t, instanceOptions, flags, storageConfig)
	if err == nil {
		return &result
	}

	t.Fatalf("failed to launch server: %v", err)
	return nil
}

func createLocalhostListenerOnFreePort() (net.Listener, int, error) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return nil, 0, err
	}

	// get port
	tcpAddr, ok := ln.Addr().(*net.TCPAddr)
	if !ok {
		ln.Close()
		return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
	}

	return ln, tcpAddr.Port, nil
}

// pkgPath returns the absolute file path to this package's directory. With go
// test, we can just look at the runtime call stack. However, bazel compiles go
// binaries with the -trimpath option so the simple approach fails however we
// can consult environment variables to derive the path.
//
// The approach taken here works for both go test and bazel on the assumption
// that if and only if trimpath is passed, we are running under bazel.
func pkgPath(t Logger) (string, error) {
	_, thisFile, _, ok := runtime.Caller(0)
	if !ok {
		return "", fmt.Errorf("failed to get current file")
	}

	pkgPath := filepath.Dir(thisFile)

	// If we find bazel env variables, then -trimpath was passed so we need to
	// construct the path from the environment.
	if testSrcdir, testWorkspace := os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE"); testSrcdir != "" && testWorkspace != "" {
		t.Logf("Detected bazel env varaiables: TEST_SRCDIR=%q TEST_WORKSPACE=%q", testSrcdir, testWorkspace)
		pkgPath = filepath.Join(testSrcdir, testWorkspace, pkgPath)
	}

	// If the path is still not absolute, something other than bazel compiled
	// with -trimpath.
	if !filepath.IsAbs(pkgPath) {
		return "", fmt.Errorf("can't construct an absolute path from %q", pkgPath)
	}

	t.Logf("Resolved testserver package path to: %q", pkgPath)

	return pkgPath, nil
}
<|endoftext|>


<commit_before>package los

import (
	"github.com/phil-mansfield/shellfish/render/io"
	"github.com/phil-mansfield/shellfish/los/geom"
)

// Halo is a _very leaky_ abstraction around the different types of halos.
// Mainly provided as a convenience for the already terrible gtet_shell.go
// file.
type Halo interface {
	GetRs(buf []float64)
	GetRhos(ring, losIdx int, buf []float64)
	MeanProfile() []float64
	MedianProfile() []float64
	Phi(i int) float64
	LineSegment(ring, losIdx int, out *geom.LineSegment)
	SheetIntersect(hd *io.SheetHeader) bool
	PlaneToVolume(ring int, px, py float64) (x, y, z float64)
	RMax() float64
}
<commit_msg>(forgor to call git rm ;__;)<commit_after><|endoftext|>


<commit_before>// Package uilive provides a writer that updates the UI
package uilive

import (
	"bytes"
	"errors"
	"io"
	"os"
	"sync"
	"time"
)

const (
	ESC = 27
)

// RefreshInterval is the default refresh interval to update the ui
var RefreshInterval = time.Millisecond

// Out is the default out for the writer
var Out = os.Stdout

// ErrClosedPipe is the error returned when trying to writer is not listening
var ErrClosedPipe = errors.New("uilive: read/write on closed pipe")

// Writer represent the writer that updates the UI
type Writer struct {
	// Out is the writer to write to
	Out io.Writer

	// RefreshInterval is the time the UI sould refresh
	RefreshInterval time.Duration

	// stopChan is buffered channel for stopping the listener
	stopChan chan struct{}
	// running is flag for determining if the listerner is running
	running bool

	buf       bytes.Buffer
	mtx       sync.Mutex
	lineCount int
}

// New returns a new writer with defaults
func New() *Writer {
	return &Writer{
		Out:             Out,
		RefreshInterval: RefreshInterval,

		stopChan: make(chan struct{}, 1),
	}
}

// Flush writes to the out and resets the buffer. It should be called after the last call to Write to ensure that any data buffered in the Writer is written to output.
// Any incomplete escape sequence at the end is considered complete for formatting purposes.
func (w *Writer) Flush() error {
	w.mtx.Lock()
	defer w.mtx.Unlock()

	// do nothing is  buffer is empty
	if len(w.buf.Bytes()) == 0 {
		return nil
	}
	w.clearLines()

	lines := 0
	for _, b := range w.buf.Bytes() {
		if b == '\n' {
			lines++
		}
	}
	w.lineCount = lines
	_, err := w.Out.Write(w.buf.Bytes())
	w.buf.Reset()
	return err
}

// Start starts the listener in a non blocking manner
func (w *Writer) Start() {
	go w.Listen()
}

// Stop stops the listener that updates the UI
func (w *Writer) Stop() {
	w.Flush()
	w.stopChan <- struct{}{}
}

// Listen listens for updates to the writers buffer and flushes to the out. It blocks the runtime.
func (w *Writer) Listen() {
	if w.running {
		return
	}
	go func() {
		w.running = true
		for {
			w.Wait()
		}
	}()
	<-w.stopChan
	w.running = false
}

// Wait waits for the writer to finish writing
func (w *Writer) Wait() {
	time.Sleep(w.RefreshInterval)
	w.Flush()
}

// Write writes buf to the writer b. The only errors returned are ones encountered while writing to the underlying output stream.
func (w *Writer) Write(buf []byte) (n int, err error) {
	w.mtx.Lock()
	defer w.mtx.Unlock()
	return w.buf.Write(buf)
}
<commit_msg>uilive: minor doc cleanup<commit_after>// Package uilive provides a writer that updates the UI
package uilive

import (
	"bytes"
	"errors"
	"io"
	"os"
	"sync"
	"time"
)

// ESC is the ASCII code for escape character
const ESC = 27

// RefreshInterval is the default refresh interval to update the ui
var RefreshInterval = time.Millisecond

// Out is the default out for the writer
var Out = os.Stdout

// ErrClosedPipe is the error returned when trying to writer is not listening
var ErrClosedPipe = errors.New("uilive: read/write on closed pipe")

// Writer represent the writer that updates the UI
type Writer struct {
	// Out is the writer to write to
	Out io.Writer

	// RefreshInterval is the time the UI sould refresh
	RefreshInterval time.Duration

	// stopChan is buffered channel for stopping the listener
	stopChan chan struct{}
	// running is flag for determining if the listerner is running
	running bool

	buf       bytes.Buffer
	mtx       sync.Mutex
	lineCount int
}

// New returns a new writer with defaults
func New() *Writer {
	return &Writer{
		Out:             Out,
		RefreshInterval: RefreshInterval,

		stopChan: make(chan struct{}, 1),
	}
}

// Flush writes to the out and resets the buffer. It should be called after the last call to Write to ensure that any data buffered in the Writer is written to output.
// Any incomplete escape sequence at the end is considered complete for formatting purposes.
func (w *Writer) Flush() error {
	w.mtx.Lock()
	defer w.mtx.Unlock()

	// do nothing is  buffer is empty
	if len(w.buf.Bytes()) == 0 {
		return nil
	}
	w.clearLines()

	lines := 0
	for _, b := range w.buf.Bytes() {
		if b == '\n' {
			lines++
		}
	}
	w.lineCount = lines
	_, err := w.Out.Write(w.buf.Bytes())
	w.buf.Reset()
	return err
}

// Start starts the listener in a non blocking manner
func (w *Writer) Start() {
	go w.Listen()
}

// Stop stops the listener that updates the UI
func (w *Writer) Stop() {
	w.Flush()
	w.stopChan <- struct{}{}
}

// Listen listens for updates to the writers buffer and flushes to the out. It blocks the runtime.
func (w *Writer) Listen() {
	if w.running {
		return
	}
	go func() {
		w.running = true
		for {
			w.Wait()
		}
	}()
	<-w.stopChan
	w.running = false
}

// Wait waits for the writer to finish writing
func (w *Writer) Wait() {
	time.Sleep(w.RefreshInterval)
	w.Flush()
}

// Write writes buf to the writer b. The only errors returned are ones encountered while writing to the underlying output stream.
func (w *Writer) Write(buf []byte) (n int, err error) {
	w.mtx.Lock()
	defer w.mtx.Unlock()
	return w.buf.Write(buf)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/tmjd/fibonacci"
	"log"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"
)

// Parses a variable n out of a POST form or query or a GET query value, all other
// methods will result in an error being returned
func getIterationCount(req *http.Request) (iterations int, err error) {
	if req.Method == "POST" {
		if strings.HasPrefix(req.Header.Get("Content-Type"), "multipart/form-data") {
			if err := req.ParseMultipartForm(1024); err != nil {
				return 0, fmt.Errorf("Bad multipart form parse: %s", err)
			}
		}
		if err := req.ParseForm(); err != nil {
			return 0, fmt.Errorf("Bad form parse: %s", err)
		}

		n, err := strconv.Atoi(req.FormValue("n"))
		if err != nil {
			return 0, fmt.Errorf("Bad value(%s) in form: %s", req.FormValue("n"), err)
		}

		return n, nil
	} else if req.Method == "GET" {
		values := req.URL.Query()
		n, err := strconv.Atoi(values.Get("n"))
		if err != nil {
			return 0, fmt.Errorf("Bad value(%s) in form: %s", values.Get("n"), err)
		}
		return n, nil
	} else {
		return 0, fmt.Errorf("Method %s not valid", req.Method)
	}
}

// Pulls FibNum(s) out of the passed in channel until it is closed and returns
// the byte slice. The output is wrapped in [] and has a comma between each element
func buildOutput(in <-chan fibonacci.FibNum) []byte {
	var output bytes.Buffer
	output.WriteString("[")
	first := true
	for num := range in {
		if first {
			first = false
		} else {
			output.WriteString(",")
		}
		output.WriteString(num.String())
	}
	output.WriteString("]")

	return output.Bytes()
}

type reqStat struct {
	duration   time.Duration
	iterations int
}

func (rs reqStat) String() string {
	return fmt.Sprintf("n=%d-%s", rs.iterations, rs.duration)
}

type statState struct {
	max_concurrent_requests int
	requests_since_trigger  int
	max_iterations          reqStat
	max_duration            reqStat
	min_duration            reqStat
}

func (ss *statState) clear() {
	ss.max_concurrent_requests = 0
	ss.requests_since_trigger = 0
	ss.max_iterations.iterations = 0
	ss.max_iterations.duration = 0
	ss.max_duration.iterations = 0
	ss.max_duration.duration = time.Since(time.Now())
	ss.min_duration.iterations = 0
	ss.min_duration.duration = time.Since(time.Now().AddDate(-1, -1, -1))
}

func (ss statState) String() string {
	return fmt.Sprintf("Requests %d Concurrent %d; MaxIterations:%s MinElapse:%s MaxElapse:%s",
		ss.requests_since_trigger, ss.max_concurrent_requests, ss.max_iterations,
		ss.min_duration, ss.max_duration)
}

// Request handler that will serve up fibonacci numbers. Also comes with a stats
// monitor that must be ran or the channels for collecting stats will fill
// and cause the handler to become blocked
type FibonacciRequestHandler struct {
	activeReq chan int
	reqStats  chan reqStat
	url_path  string
}

// These is our dependency injection for testing
var timeTriggerDelay = time.After
var statSelectDone = func() {}
var writeLogMsg = log.Printf

func clearInjectionPoints() {
	timeTriggerDelay = time.After
	statSelectDone = func() {}
	writeLogMsg = log.Printf
}

// Periodically prints out the stats over the last 2 seconds if there are or have
// been any requests handled
func (frh *FibonacciRequestHandler) statsMonitor() {
	var state statState
	state.clear()
	cur_req := 0

	printDelay, _ := time.ParseDuration("2s")
	timeTrigger := timeTriggerDelay(printDelay)
	for {
		select {
		case req := <-frh.activeReq:
			cur_req = cur_req + req

			if req == 1 {
				state.requests_since_trigger = state.requests_since_trigger + 1
			}

			if cur_req > state.max_concurrent_requests {
				state.max_concurrent_requests = cur_req
			}
		case stat := <-frh.reqStats:
			if state.max_duration.duration.Nanoseconds() < stat.duration.Nanoseconds() {
				state.max_duration = stat
			}
			if state.min_duration.duration.Nanoseconds() > stat.duration.Nanoseconds() {
				state.min_duration = stat
			}
			if state.max_iterations.iterations < stat.iterations {
				state.max_iterations = stat
			}
		case <-timeTrigger:
			if state.max_concurrent_requests != 0 {
				writeLogMsg("Fibonacci stats: %s", state)
			}
			state.clear()
			// Immediately set the max to the cur_req
			state.max_concurrent_requests = cur_req

			// Reset the timeTrigger
			timeTrigger = timeTriggerDelay(printDelay)
		}
		statSelectDone() //Injection point for testing
	}
}

// Create new fibonacci request handler and setup the channels used for stats collection
func NewFibonacciRequestHandler(url_path string) *FibonacciRequestHandler {
	var frh FibonacciRequestHandler
	frh.activeReq = make(chan int, 100)
	frh.reqStats = make(chan reqStat, 100)
	frh.url_path = path.Clean("/" + url_path)
	return &frh
}

func respondToUnsupportedMethod(res http.ResponseWriter, req *http.Request) {
	http.Error(res, fmt.Sprintf("%q unsupported", req.Method), http.StatusMethodNotAllowed)
	writeLogMsg("%q", req)
}

// Handler for generating fibonacci numbers, expects a variable n to be set through
// a POST form or query or a GET query value
func (frh *FibonacciRequestHandler) FibonacciRequestHandleFunc(res http.ResponseWriter, req *http.Request) {
	frh.activeReq <- 1
	start := time.Now()
	var stat reqStat
	defer func() {
		frh.activeReq <- -1
		stat.duration = time.Since(start)
		frh.reqStats <- stat
	}()

	if req.URL.Path != frh.url_path {
		msg := fmt.Sprintf("Request path (%s) does not match %s", req.URL.Path, frh.url_path)
		writeLogMsg("%s, respond with code StatusNotFound", msg)
		http.Error(res, msg, http.StatusNotFound)
		return
	}
	if req.Method != "POST" && req.Method != "GET" {
		respondToUnsupportedMethod(res, req)
		return
	}

	n, err := getIterationCount(req)
	if err != nil {
		http.Error(res, err.Error(), http.StatusBadRequest)
		return
	}

	stat.iterations = n
	fg, err := fibonacci.NewGenerator(n)
	if err != nil {
		http.Error(res, err.Error(), http.StatusBadRequest)
		writeLogMsg("FibonacciGenerator reported %q from request %q", err, req)
		return
	}

	nums := make(chan fibonacci.FibNum)
	go fg.Produce(nums)
	output := buildOutput(nums)

	_, err = res.Write(output)
	if err != nil {
		writeLogMsg("Error (%s) while writing response for %q", err, req.Host)
	}
}

func main() {

	sm := http.NewServeMux()
	frh := NewFibonacciRequestHandler("/fibonacci")
	sm.HandleFunc("/fibonacci", frh.FibonacciRequestHandleFunc)

	// Must run the stats monitor or the stats channels will fill and block requests
	go frh.statsMonitor()

	log.Fatal(http.ListenAndServe(":8080", sm))
}
<commit_msg>Added flags and parsing so the serve path and port can be set.<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/tmjd/fibonacci"
	"log"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"
)

// Parses a variable n out of a POST form or query or a GET query value, all other
// methods will result in an error being returned
func getIterationCount(req *http.Request) (iterations int, err error) {
	if req.Method == "POST" {
		if strings.HasPrefix(req.Header.Get("Content-Type"), "multipart/form-data") {
			if err := req.ParseMultipartForm(1024); err != nil {
				return 0, fmt.Errorf("Bad multipart form parse: %s", err)
			}
		}
		if err := req.ParseForm(); err != nil {
			return 0, fmt.Errorf("Bad form parse: %s", err)
		}

		n, err := strconv.Atoi(req.FormValue("n"))
		if err != nil {
			return 0, fmt.Errorf("Bad value(%s) in form: %s", req.FormValue("n"), err)
		}

		return n, nil
	} else if req.Method == "GET" {
		values := req.URL.Query()
		n, err := strconv.Atoi(values.Get("n"))
		if err != nil {
			return 0, fmt.Errorf("Bad value(%s) in form: %s", values.Get("n"), err)
		}
		return n, nil
	} else {
		return 0, fmt.Errorf("Method %s not valid", req.Method)
	}
}

// Pulls FibNum(s) out of the passed in channel until it is closed and returns
// the byte slice. The output is wrapped in [] and has a comma between each element
func buildOutput(in <-chan fibonacci.FibNum) []byte {
	var output bytes.Buffer
	output.WriteString("[")
	first := true
	for num := range in {
		if first {
			first = false
		} else {
			output.WriteString(",")
		}
		output.WriteString(num.String())
	}
	output.WriteString("]")

	return output.Bytes()
}

type reqStat struct {
	duration   time.Duration
	iterations int
}

func (rs reqStat) String() string {
	return fmt.Sprintf("n=%d-%s", rs.iterations, rs.duration)
}

type statState struct {
	max_concurrent_requests int
	requests_since_trigger  int
	max_iterations          reqStat
	max_duration            reqStat
	min_duration            reqStat
}

func (ss *statState) clear() {
	ss.max_concurrent_requests = 0
	ss.requests_since_trigger = 0
	ss.max_iterations.iterations = 0
	ss.max_iterations.duration = 0
	ss.max_duration.iterations = 0
	ss.max_duration.duration = time.Since(time.Now())
	ss.min_duration.iterations = 0
	ss.min_duration.duration = time.Since(time.Now().AddDate(-1, -1, -1))
}

func (ss statState) String() string {
	return fmt.Sprintf("Requests %d Concurrent %d; MaxIterations:%s MinElapse:%s MaxElapse:%s",
		ss.requests_since_trigger, ss.max_concurrent_requests, ss.max_iterations,
		ss.min_duration, ss.max_duration)
}

// Request handler that will serve up fibonacci numbers. Also comes with a stats
// monitor that must be ran or the channels for collecting stats will fill
// and cause the handler to become blocked
type FibonacciRequestHandler struct {
	activeReq chan int
	reqStats  chan reqStat
	url_path  string
}

// These is our dependency injection for testing
var timeTriggerDelay = time.After
var statSelectDone = func() {}
var writeLogMsg = log.Printf

func clearInjectionPoints() {
	timeTriggerDelay = time.After
	statSelectDone = func() {}
	writeLogMsg = log.Printf
}

// Periodically prints out the stats over the last 2 seconds if there are or have
// been any requests handled
func (frh *FibonacciRequestHandler) statsMonitor() {
	var state statState
	state.clear()
	cur_req := 0

	printDelay, _ := time.ParseDuration("2s")
	timeTrigger := timeTriggerDelay(printDelay)
	for {
		select {
		case req := <-frh.activeReq:
			cur_req = cur_req + req

			if req == 1 {
				state.requests_since_trigger = state.requests_since_trigger + 1
			}

			if cur_req > state.max_concurrent_requests {
				state.max_concurrent_requests = cur_req
			}
		case stat := <-frh.reqStats:
			if state.max_duration.duration.Nanoseconds() < stat.duration.Nanoseconds() {
				state.max_duration = stat
			}
			if state.min_duration.duration.Nanoseconds() > stat.duration.Nanoseconds() {
				state.min_duration = stat
			}
			if state.max_iterations.iterations < stat.iterations {
				state.max_iterations = stat
			}
		case <-timeTrigger:
			if state.max_concurrent_requests != 0 {
				writeLogMsg("Fibonacci stats: %s", state)
			}
			state.clear()
			// Immediately set the max to the cur_req
			state.max_concurrent_requests = cur_req

			// Reset the timeTrigger
			timeTrigger = timeTriggerDelay(printDelay)
		}
		statSelectDone() //Injection point for testing
	}
}

// Create new fibonacci request handler and setup the channels used for stats collection
func NewFibonacciRequestHandler(url_path string) *FibonacciRequestHandler {
	var frh FibonacciRequestHandler
	frh.activeReq = make(chan int, 100)
	frh.reqStats = make(chan reqStat, 100)
	frh.url_path = path.Clean("/" + url_path)
	return &frh
}

func respondToUnsupportedMethod(res http.ResponseWriter, req *http.Request) {
	http.Error(res, fmt.Sprintf("%q unsupported", req.Method), http.StatusMethodNotAllowed)
	writeLogMsg("%q", req)
}

// Handler for generating fibonacci numbers, expects a variable n to be set through
// a POST form or query or a GET query value
func (frh *FibonacciRequestHandler) FibonacciRequestHandleFunc(res http.ResponseWriter, req *http.Request) {
	frh.activeReq <- 1
	start := time.Now()
	var stat reqStat
	defer func() {
		frh.activeReq <- -1
		stat.duration = time.Since(start)
		frh.reqStats <- stat
	}()

	// If the path does not match exactly then response with error
	if req.URL.Path != frh.url_path {
		msg := fmt.Sprintf("Request path (%s) does not match %s", req.URL.Path, frh.url_path)
		writeLogMsg("%s, respond with code StatusNotFound", msg)
		http.Error(res, msg, http.StatusNotFound)
		return
	}
	if req.Method != "POST" && req.Method != "GET" {
		respondToUnsupportedMethod(res, req)
		return
	}

	n, err := getIterationCount(req)
	if err != nil {
		http.Error(res, err.Error(), http.StatusBadRequest)
		return
	}

	stat.iterations = n
	fg, err := fibonacci.NewGenerator(n)
	if err != nil {
		http.Error(res, err.Error(), http.StatusBadRequest)
		writeLogMsg("FibonacciGenerator reported %q from request %q", err, req)
		return
	}

	nums := make(chan fibonacci.FibNum)
	go fg.Produce(nums)
	output := buildOutput(nums)

	_, err = res.Write(output)
	if err != nil {
		writeLogMsg("Error (%s) while writing response for %q", err, req.Host)
	}
}

var serve_path string
var port int

func init() {
	flag.StringVar(&serve_path, "serve_path", "fibonacci",
		"the path from root that will access the RestAPI")
	flag.IntVar(&port, "port", 8080, "port where the server will listen")
}

func main() {
	flag.Parse()

	serve_path = path.Clean(fmt.Sprintf("/%s", serve_path))

	writeLogMsg("FibonacciServer listening on port %d at path %s", port, serve_path)

	sm := http.NewServeMux()
	frh := NewFibonacciRequestHandler(serve_path)
	sm.HandleFunc(serve_path, frh.FibonacciRequestHandleFunc)

	// Must run the stats monitor or the stats channels will fill and block requests
	go frh.statsMonitor()

	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), sm))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"github.com/lxc/lxd/shared"
	"gopkg.in/lxc/go-lxc.v2"
)

func runCommand(container *lxc.Container, command []string, options lxc.AttachOptions) shared.OperationResult {
	status, err := container.RunCommandStatus(command, options)
	if err != nil {
		shared.Debugf("Failed running command: %q", err.Error())
		return shared.OperationError(err)
	}

	metadata, err := json.Marshal(shared.Jmap{"return": status})
	if err != nil {
		return shared.OperationError(err)
	}

	return shared.OperationResult{Metadata: metadata, Error: nil}
}

func (s *execWs) Metadata() interface{} {
	fds := shared.Jmap{}
	for fd, secret := range s.fds {
		if fd == -1 {
			fds["control"] = secret
		} else {
			fds[strconv.Itoa(fd)] = secret
		}
	}

	return shared.Jmap{"fds": fds}
}

func (s *execWs) Connect(secret string, r *http.Request, w http.ResponseWriter) error {
	for fd, fdSecret := range s.fds {
		if secret == fdSecret {
			conn, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
			if err != nil {
				return err
			}

			s.conns[fd] = conn

			if fd == -1 {
				s.controlConnected <- true
				return nil
			}

			for i, c := range s.conns {
				if i != -1 && c == nil {
					return nil
				}
			}
			s.allConnected <- true
			return nil
		}
	}

	/* If we didn't find the right secret, the user provided a bad one,
	 * which 403, not 404, since this operation actually exists */
	return os.ErrPermission
}

func (s *execWs) Do() shared.OperationResult {
	<-s.allConnected

	var err error
	var ttys []*os.File
	var ptys []*os.File

	if s.interactive {
		ttys = make([]*os.File, 1)
		ptys = make([]*os.File, 1)
		ptys[0], ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
		s.options.StdinFd = ttys[0].Fd()
		s.options.StdoutFd = ttys[0].Fd()
		s.options.StderrFd = ttys[0].Fd()
	} else {
		ttys = make([]*os.File, 3)
		ptys = make([]*os.File, 3)
		for i := 0; i < len(ttys); i++ {
			ptys[i], ttys[i], err = shared.Pipe()
			if err != nil {
				return shared.OperationError(err)
			}
		}
		s.options.StdinFd = ptys[0].Fd()
		s.options.StdoutFd = ttys[1].Fd()
		s.options.StderrFd = ttys[2].Fd()
	}

	controlExit := make(chan bool)
	stdEOF := make(chan bool)

	if s.interactive {
		go func() {
			select {
			case <-s.controlConnected:
				break

			case <-controlExit:
				return
			}

			for {
				mt, r, err := s.conns[-1].NextReader()
				if mt == websocket.CloseMessage {
					break
				}

				if err != nil {
					shared.Debugf("Got error getting next reader %s", err)
					break
				}

				buf, err := ioutil.ReadAll(r)
				if err != nil {
					shared.Debugf("Failed to read message %s", err)
					break
				}

				command := shared.ContainerExecControl{}

				if err := json.Unmarshal(buf, &command); err != nil {
					shared.Debugf("Failed to unmarshal control socket command: %s", err)
					continue
				}

				if command.Command == "window-resize" {
					winchWidth, err := strconv.Atoi(command.Args["width"])
					if err != nil {
						shared.Debugf("Unable to extract window width: %s", err)
						continue
					}

					winchHeight, err := strconv.Atoi(command.Args["height"])
					if err != nil {
						shared.Debugf("Unable to extract window height: %s", err)
						continue
					}

					err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
					if err != nil {
						shared.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
						continue
					}
				}

				if err != nil {
					shared.Debugf("Got error writing to writer %s", err)
					break
				}
			}
		}()

		shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0])
	} else {
		for i := 0; i < len(ttys); i++ {
			go func(i int) {
				if i == 0 {
					<-shared.WebsocketRecvStream(ttys[i], s.conns[i])
					ttys[i].Close()
				} else {
					<-shared.WebsocketSendStream(s.conns[i], ptys[i])
					ptys[i].Close()
					stdEOF <- true
				}
			}(i)
		}
	}

	result := runCommand(
		s.container,
		s.command,
		s.options,
	)

	if !s.interactive {
		ttys[0].Close()
		ttys[1].Close()
		ttys[2].Close()
		<-stdEOF
	}

	for _, tty := range ttys {
		tty.Close()
	}

	for _, pty := range ptys {
		pty.Close()
	}

	if s.interactive && s.conns[-1] == nil {
		controlExit <- true
	}

	return result
}

func containerExecPost(d *Daemon, r *http.Request) Response {
	name := mux.Vars(r)["name"]
	c, err := containerLXDLoad(d, name)
	if err != nil {
		return SmartError(err)
	}

	if !c.IsRunning() {
		return BadRequest(fmt.Errorf("Container is not running."))
	}

	post := commandPostContent{}
	buf, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return BadRequest(err)
	}

	if err := json.Unmarshal(buf, &post); err != nil {
		return BadRequest(err)
	}

	opts := lxc.DefaultAttachOptions
	opts.ClearEnv = true
	opts.Env = []string{}

	for k, v := range c.ConfigGet() {
		if strings.HasPrefix(k, "environment.") {
			opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", strings.TrimPrefix(k, "environment."), v))
		}
	}

	if post.Environment != nil {
		for k, v := range post.Environment {
			if k == "HOME" {
				opts.Cwd = v
			}
			opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", k, v))
		}
	}

	if post.WaitForWS {
		ws := &execWs{}
		ws.fds = map[int]string{}
		idmapset := c.IdmapSetGet()
		if idmapset != nil {
			ws.rootUid, ws.rootGid = idmapset.ShiftIntoNs(0, 0)
		}
		ws.conns = map[int]*websocket.Conn{}
		ws.conns[-1] = nil
		ws.conns[0] = nil
		if !post.Interactive {
			ws.conns[1] = nil
			ws.conns[2] = nil
		}
		ws.allConnected = make(chan bool, 1)
		ws.controlConnected = make(chan bool, 1)
		ws.interactive = post.Interactive
		ws.done = make(chan shared.OperationResult, 1)
		ws.options = opts
		for i := -1; i < len(ws.conns)-1; i++ {
			ws.fds[i], err = shared.RandomCryptoString()
			if err != nil {
				return InternalError(err)
			}
		}

		ws.command = post.Command
		ws.container = c.LXContainerGet()

		return AsyncResponseWithWs(ws, nil)
	}

	run := func() shared.OperationResult {

		nullDev, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
		if err != nil {
			return shared.OperationError(err)
		}
		defer nullDev.Close()
		nullfd := nullDev.Fd()

		opts.StdinFd = nullfd
		opts.StdoutFd = nullfd
		opts.StderrFd = nullfd

		return runCommand(c.LXContainerGet(), post.Command, opts)
	}

	return AsyncResponse(run, nil)
}
<commit_msg>Fix resource leakage in non-interactive exec<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"github.com/lxc/lxd/shared"
	"gopkg.in/lxc/go-lxc.v2"
)

func runCommand(container *lxc.Container, command []string, options lxc.AttachOptions) shared.OperationResult {
	status, err := container.RunCommandStatus(command, options)
	if err != nil {
		shared.Debugf("Failed running command: %q", err.Error())
		return shared.OperationError(err)
	}

	metadata, err := json.Marshal(shared.Jmap{"return": status})
	if err != nil {
		return shared.OperationError(err)
	}

	return shared.OperationResult{Metadata: metadata, Error: nil}
}

func (s *execWs) Metadata() interface{} {
	fds := shared.Jmap{}
	for fd, secret := range s.fds {
		if fd == -1 {
			fds["control"] = secret
		} else {
			fds[strconv.Itoa(fd)] = secret
		}
	}

	return shared.Jmap{"fds": fds}
}

func (s *execWs) Connect(secret string, r *http.Request, w http.ResponseWriter) error {
	for fd, fdSecret := range s.fds {
		if secret == fdSecret {
			conn, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
			if err != nil {
				return err
			}

			s.conns[fd] = conn

			if fd == -1 {
				s.controlConnected <- true
				return nil
			}

			for i, c := range s.conns {
				if i != -1 && c == nil {
					return nil
				}
			}
			s.allConnected <- true
			return nil
		}
	}

	/* If we didn't find the right secret, the user provided a bad one,
	 * which 403, not 404, since this operation actually exists */
	return os.ErrPermission
}

func (s *execWs) Do() shared.OperationResult {
	<-s.allConnected

	var err error
	var ttys []*os.File
	var ptys []*os.File

	if s.interactive {
		ttys = make([]*os.File, 1)
		ptys = make([]*os.File, 1)
		ptys[0], ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
		s.options.StdinFd = ttys[0].Fd()
		s.options.StdoutFd = ttys[0].Fd()
		s.options.StderrFd = ttys[0].Fd()
	} else {
		ttys = make([]*os.File, 3)
		ptys = make([]*os.File, 3)
		for i := 0; i < len(ttys); i++ {
			ptys[i], ttys[i], err = shared.Pipe()
			if err != nil {
				return shared.OperationError(err)
			}
		}
		s.options.StdinFd = ptys[0].Fd()
		s.options.StdoutFd = ttys[1].Fd()
		s.options.StderrFd = ttys[2].Fd()
	}

	controlExit := make(chan bool)
	var wgEOF sync.WaitGroup

	if s.interactive {
		go func() {
			select {
			case <-s.controlConnected:
				break

			case <-controlExit:
				return
			}

			for {
				mt, r, err := s.conns[-1].NextReader()
				if mt == websocket.CloseMessage {
					break
				}

				if err != nil {
					shared.Debugf("Got error getting next reader %s", err)
					break
				}

				buf, err := ioutil.ReadAll(r)
				if err != nil {
					shared.Debugf("Failed to read message %s", err)
					break
				}

				command := shared.ContainerExecControl{}

				if err := json.Unmarshal(buf, &command); err != nil {
					shared.Debugf("Failed to unmarshal control socket command: %s", err)
					continue
				}

				if command.Command == "window-resize" {
					winchWidth, err := strconv.Atoi(command.Args["width"])
					if err != nil {
						shared.Debugf("Unable to extract window width: %s", err)
						continue
					}

					winchHeight, err := strconv.Atoi(command.Args["height"])
					if err != nil {
						shared.Debugf("Unable to extract window height: %s", err)
						continue
					}

					err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
					if err != nil {
						shared.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
						continue
					}
				}

				if err != nil {
					shared.Debugf("Got error writing to writer %s", err)
					break
				}
			}
		}()

		shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0])
	} else {
		wgEOF.Add(len(ttys) - 1)
		for i := 0; i < len(ttys); i++ {
			go func(i int) {
				if i == 0 {
					<-shared.WebsocketRecvStream(ttys[i], s.conns[i])
					ttys[i].Close()
				} else {
					<-shared.WebsocketSendStream(s.conns[i], ptys[i])
					ptys[i].Close()
					wgEOF.Done()
				}
			}(i)
		}
	}

	result := runCommand(
		s.container,
		s.command,
		s.options,
	)

	if !s.interactive {
		ttys[0].Close()
		ttys[1].Close()
		ttys[2].Close()
		wgEOF.Wait()
	}

	for _, tty := range ttys {
		tty.Close()
	}

	for _, pty := range ptys {
		pty.Close()
	}

	if s.interactive && s.conns[-1] == nil {
		controlExit <- true
	}

	return result
}

func containerExecPost(d *Daemon, r *http.Request) Response {
	name := mux.Vars(r)["name"]
	c, err := containerLXDLoad(d, name)
	if err != nil {
		return SmartError(err)
	}

	if !c.IsRunning() {
		return BadRequest(fmt.Errorf("Container is not running."))
	}

	post := commandPostContent{}
	buf, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return BadRequest(err)
	}

	if err := json.Unmarshal(buf, &post); err != nil {
		return BadRequest(err)
	}

	opts := lxc.DefaultAttachOptions
	opts.ClearEnv = true
	opts.Env = []string{}

	for k, v := range c.ConfigGet() {
		if strings.HasPrefix(k, "environment.") {
			opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", strings.TrimPrefix(k, "environment."), v))
		}
	}

	if post.Environment != nil {
		for k, v := range post.Environment {
			if k == "HOME" {
				opts.Cwd = v
			}
			opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", k, v))
		}
	}

	if post.WaitForWS {
		ws := &execWs{}
		ws.fds = map[int]string{}
		idmapset := c.IdmapSetGet()
		if idmapset != nil {
			ws.rootUid, ws.rootGid = idmapset.ShiftIntoNs(0, 0)
		}
		ws.conns = map[int]*websocket.Conn{}
		ws.conns[-1] = nil
		ws.conns[0] = nil
		if !post.Interactive {
			ws.conns[1] = nil
			ws.conns[2] = nil
		}
		ws.allConnected = make(chan bool, 1)
		ws.controlConnected = make(chan bool, 1)
		ws.interactive = post.Interactive
		ws.done = make(chan shared.OperationResult, 1)
		ws.options = opts
		for i := -1; i < len(ws.conns)-1; i++ {
			ws.fds[i], err = shared.RandomCryptoString()
			if err != nil {
				return InternalError(err)
			}
		}

		ws.command = post.Command
		ws.container = c.LXContainerGet()

		return AsyncResponseWithWs(ws, nil)
	}

	run := func() shared.OperationResult {

		nullDev, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
		if err != nil {
			return shared.OperationError(err)
		}
		defer nullDev.Close()
		nullfd := nullDev.Fd()

		opts.StdinFd = nullfd
		opts.StdoutFd = nullfd
		opts.StderrFd = nullfd

		return runCommand(c.LXContainerGet(), post.Command, opts)
	}

	return AsyncResponse(run, nil)
}
<|endoftext|>


<commit_before>package node_test

import (
	"testing"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/node"
	"github.com/mpvl/subtest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// The raft identity (ID and address) of a node depends on the value of
// cluster.https_address and the entries of the raft_nodes table.
func TestDetermineRaftNode(t *testing.T) {
	cases := []struct {
		title     string
		address   string       // Value of cluster.https_address
		addresses []string     // Entries in raft_nodes
		node      *db.RaftNode // Expected node value
	}{
		{
			`no cluster.https_address set`,
			"",
			[]string{},
			&db.RaftNode{ID: 1},
		},
		{
			`cluster.https_address set and and no raft_nodes rows`,
			"1.2.3.4:8443",
			[]string{},
			&db.RaftNode{ID: 1},
		},
		{
			`cluster.https_address set and matching the one and only raft_nodes row`,
			"1.2.3.4:8443",
			[]string{"1.2.3.4:8443"},
			&db.RaftNode{ID: 1, Address: "1.2.3.4:8443"},
		},
		{
			`cluster.https_address set and matching one of many raft_nodes rows`,
			"5.6.7.8:999",
			[]string{"1.2.3.4:666", "5.6.7.8:999"},
			&db.RaftNode{ID: 2, Address: "5.6.7.8:999"},
		},
		{
			`core.cluster set and no matching raft_nodes row`,
			"1.2.3.4:666",
			[]string{"5.6.7.8:999"},
			nil,
		},
	}

	for _, c := range cases {
		subtest.Run(t, c.title, func(t *testing.T) {
			tx, cleanup := db.NewTestNodeTx(t)
			defer cleanup()

			err := tx.UpdateConfig(map[string]string{"cluster.https_address": c.address})
			require.NoError(t, err)

			for _, address := range c.addresses {
				_, err := tx.CreateRaftNode(address)
				require.NoError(t, err)
			}

			node, err := node.DetermineRaftNode(tx)
			require.NoError(t, err)
			if c.node == nil {
				assert.Nil(t, node)
			} else {
				assert.Equal(t, c.node.ID, node.ID)
				assert.Equal(t, c.node.Address, node.Address)
			}
		})
	}
}
<commit_msg>lxd/node/raft/test: Corrects typo<commit_after>package node_test

import (
	"testing"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/node"
	"github.com/mpvl/subtest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// The raft identity (ID and address) of a node depends on the value of
// cluster.https_address and the entries of the raft_nodes table.
func TestDetermineRaftNode(t *testing.T) {
	cases := []struct {
		title     string
		address   string       // Value of cluster.https_address
		addresses []string     // Entries in raft_nodes
		node      *db.RaftNode // Expected node value
	}{
		{
			`no cluster.https_address set`,
			"",
			[]string{},
			&db.RaftNode{ID: 1},
		},
		{
			`cluster.https_address set and no raft_nodes rows`,
			"1.2.3.4:8443",
			[]string{},
			&db.RaftNode{ID: 1},
		},
		{
			`cluster.https_address set and matching the one and only raft_nodes row`,
			"1.2.3.4:8443",
			[]string{"1.2.3.4:8443"},
			&db.RaftNode{ID: 1, Address: "1.2.3.4:8443"},
		},
		{
			`cluster.https_address set and matching one of many raft_nodes rows`,
			"5.6.7.8:999",
			[]string{"1.2.3.4:666", "5.6.7.8:999"},
			&db.RaftNode{ID: 2, Address: "5.6.7.8:999"},
		},
		{
			`core.cluster set and no matching raft_nodes row`,
			"1.2.3.4:666",
			[]string{"5.6.7.8:999"},
			nil,
		},
	}

	for _, c := range cases {
		subtest.Run(t, c.title, func(t *testing.T) {
			tx, cleanup := db.NewTestNodeTx(t)
			defer cleanup()

			err := tx.UpdateConfig(map[string]string{"cluster.https_address": c.address})
			require.NoError(t, err)

			for _, address := range c.addresses {
				_, err := tx.CreateRaftNode(address)
				require.NoError(t, err)
			}

			node, err := node.DetermineRaftNode(tx)
			require.NoError(t, err)
			if c.node == nil {
				assert.Nil(t, node)
			} else {
				assert.Equal(t, c.node.ID, node.ID)
				assert.Equal(t, c.node.Address, node.Address)
			}
		})
	}
}
<|endoftext|>