<commit_before>package db

import (
	"encoding/binary"
	"github.com/google/btree"
	turtle "github.com/gtfierro/hod/goraptor"
)

/*
Need to think of the data structure and API for the item that will be storing/collating
the results of the query.
- resolve{Sub,Obj}: variable gets resolved an added to top level scope:
    addVariable(varname, tree), where 'tree' is a btree of ResultEntity
*/

type ResultEntity struct {
	PK   [4]byte
	Next map[string]*btree.BTree
}

func (re ResultEntity) Less(than btree.Item) bool {
	t := than.(*ResultEntity)
	return binary.LittleEndian.Uint32(re.PK[:]) < binary.LittleEndian.Uint32(t.PK[:])
}

// map wrapper for storing intermediate results
type resultMap struct {
	vars     map[string]*btree.BTree
	varOrder *variableStateMap
	tuples   *btree.BTree
}

func (rm *resultMap) has(variable string) bool {
	_, found := rm.vars[variable]
	return found
}

func (rm *resultMap) addVariable(variable string, tree *btree.BTree) {
	rm.vars[variable] = hashTreeToEntityTree(tree)
}

func (rm *resultMap) getVariableChain(variable string) []string {
	var _getchain func(rm *resultMap, variable string)
	chain := []string{variable}
	_getchain = func(rm *resultMap, variable string) {
		next := rm.varOrder.vars[variable]
		if next != RESOLVED {
			chain = append([]string{next}, chain...)
			_getchain(rm, next)
		}
	}
	_getchain(rm, variable)
	return chain
}

func (rm *resultMap) replaceEntity(varname string, entity *ResultEntity) bool {
	var replaceInTree func(*btree.BTree, []string, *ResultEntity) bool
	chain := rm.getVariableChain(varname)
	replaceInTree = func(tree *btree.BTree, varorder []string, entity *ResultEntity) bool {
		if tree.Has(entity) {
			tree.ReplaceOrInsert(entity)
			return true
		}
		finishedReplace := false
		iter := func(i btree.Item) bool {
			ent := i.(*ResultEntity)
			if ntree, found := ent.Next[varname]; found {
				if replaceInTree(ntree, varorder[1:], entity) {
					finishedReplace = true
					return false // stop iteration
				}
			}
			if len(varorder) == 0 {
				return i != tree.Max()
			}
			if ntree, found := ent.Next[varorder[0]]; found {
				if replaceInTree(ntree, varorder[1:], entity) {
					finishedReplace = true
					return false // stop iteration
				}
			}
			return i != tree.Max()
		}
		tree.Ascend(iter)
		return finishedReplace
	}
	return replaceInTree(rm.vars[chain[0]], chain[1:], entity)
}

// iterates through all the entries we have for variable
func (rm *resultMap) iterVariable(variable string) []*ResultEntity {
	var _iterbtree func(btree *btree.BTree, itervars []string)
	var results []*ResultEntity
	iterorder := rm.getVariableChain(variable)
	if len(iterorder) == 0 {
		panic("no order for variable " + variable)
	}
	if rm.varOrder.vars[variable] == RESOLVED { // top level
		tree := rm.vars[variable]
		iter := func(i btree.Item) bool {
			results = append(results, i.(*ResultEntity))
			return i != tree.Max()
		}
		tree.Ascend(iter)
		return results
	}
	_iterbtree = func(tree *btree.BTree, itervars []string) {
		iter := func(i btree.Item) bool {
			entity := i.(*ResultEntity)
			if len(itervars) == 0 {
				results = append(results, entity)
				return i != tree.Max()
			}
			if subtree, found := entity.Next[variable]; found {
				_iterbtree(subtree, itervars[1:])
			} else {
				_iterbtree(entity.Next[itervars[0]], itervars[1:])
			}
			return i != tree.Max()
		}
		if tree == nil {
			return
		}
		tree.Ascend(iter)
	}
	tree := rm.vars[iterorder[0]]
	_iterbtree(tree, iterorder[1:])
	return results
}

// check the variable status.
// because variables can be nested, we need to figure out how to filter those out
func (rm *resultMap) getVar(variable string) *btree.BTree {
	if tree, found := rm.vars[variable]; found {
		return tree
	}
	return nil
}

func newResultMap() *resultMap {
	return &resultMap{
		vars:   make(map[string]*btree.BTree),
		tuples: btree.New(3),
	}
}

func (db *DB) expandTuples(rm *resultMap, selectVars []string) [][]turtle.URI {
	var tuples []map[string]turtle.URI
	var startvar string
	for v, state := range rm.varOrder.vars {
		if state == RESOLVED {
			startvar = v
			break
		}
	}
	tree := rm.vars[startvar]
	iter := func(i btree.Item) bool {
		entity := i.(*ResultEntity)
		newtups := db._getTuplesFromTree(startvar, entity)
		tuples = append(tuples, newtups...)
		return i != tree.Max()
	}
	tree.Ascend(iter)

	var results [][]turtle.URI
tupleLoop:
	for _, tup := range tuples {
		var row []turtle.URI
		for _, varname := range selectVars {
			if _, found := tup[varname]; !found {
				continue tupleLoop
			}
			row = append(row, tup[varname])
		}
		results = append(results, row)
	}
	return results
}

func (db *DB) _getTuplesFromTree(name string, ve *ResultEntity) []map[string]turtle.URI {
	uri := db.MustGetURI(ve.PK)
	var ret []map[string]turtle.URI
	if len(ve.Next) == 0 {
		ret = append(ret, map[string]turtle.URI{name: uri})
	} else {
		for lname, etree := range ve.Next {
			vars := make(map[string]turtle.URI)
			vars[name] = uri
			iter := func(i btree.Item) bool {
				entity := i.(*ResultEntity)
				for _, m := range db._getTuplesFromTree(lname, entity) {
					for k, v := range m {
						vars[k] = v
					}
				}
				return i != etree.Max()
			}
			etree.Ascend(iter)
			ret = append(ret, vars)
		}
	}
	return ret
}
<commit_msg>fix tuple reporting<commit_after>package db

import (
	"encoding/binary"
	"github.com/google/btree"
	turtle "github.com/gtfierro/hod/goraptor"
)

/*
Need to think of the data structure and API for the item that will be storing/collating
the results of the query.
- resolve{Sub,Obj}: variable gets resolved an added to top level scope:
    addVariable(varname, tree), where 'tree' is a btree of ResultEntity
*/

type ResultEntity struct {
	PK   [4]byte
	Next map[string]*btree.BTree
}

func (re ResultEntity) Less(than btree.Item) bool {
	t := than.(*ResultEntity)
	return binary.LittleEndian.Uint32(re.PK[:]) < binary.LittleEndian.Uint32(t.PK[:])
}

// map wrapper for storing intermediate results
type resultMap struct {
	vars     map[string]*btree.BTree
	varOrder *variableStateMap
	tuples   *btree.BTree
}

func (rm *resultMap) has(variable string) bool {
	_, found := rm.vars[variable]
	return found
}

func (rm *resultMap) addVariable(variable string, tree *btree.BTree) {
	rm.vars[variable] = hashTreeToEntityTree(tree)
}

func (rm *resultMap) getVariableChain(variable string) []string {
	var _getchain func(rm *resultMap, variable string)
	chain := []string{variable}
	_getchain = func(rm *resultMap, variable string) {
		next := rm.varOrder.vars[variable]
		if next != RESOLVED {
			chain = append([]string{next}, chain...)
			_getchain(rm, next)
		}
	}
	_getchain(rm, variable)
	return chain
}

func (rm *resultMap) replaceEntity(varname string, entity *ResultEntity) bool {
	var replaceInTree func(*btree.BTree, []string, *ResultEntity) bool
	chain := rm.getVariableChain(varname)
	replaceInTree = func(tree *btree.BTree, varorder []string, entity *ResultEntity) bool {
		if tree.Has(entity) {
			tree.ReplaceOrInsert(entity)
			return true
		}
		finishedReplace := false
		iter := func(i btree.Item) bool {
			ent := i.(*ResultEntity)
			if ntree, found := ent.Next[varname]; found {
				if replaceInTree(ntree, varorder[1:], entity) {
					finishedReplace = true
					return false // stop iteration
				}
			}
			if len(varorder) == 0 {
				return i != tree.Max()
			}
			if ntree, found := ent.Next[varorder[0]]; found {
				if replaceInTree(ntree, varorder[1:], entity) {
					finishedReplace = true
					return false // stop iteration
				}
			}
			return i != tree.Max()
		}
		tree.Ascend(iter)
		return finishedReplace
	}
	return replaceInTree(rm.vars[chain[0]], chain[1:], entity)
}

// iterates through all the entries we have for variable
func (rm *resultMap) iterVariable(variable string) []*ResultEntity {
	var _iterbtree func(btree *btree.BTree, itervars []string)
	var results []*ResultEntity
	iterorder := rm.getVariableChain(variable)
	if len(iterorder) == 0 {
		panic("no order for variable " + variable)
	}
	if rm.varOrder.vars[variable] == RESOLVED { // top level
		tree := rm.vars[variable]
		iter := func(i btree.Item) bool {
			results = append(results, i.(*ResultEntity))
			return i != tree.Max()
		}
		tree.Ascend(iter)
		return results
	}
	_iterbtree = func(tree *btree.BTree, itervars []string) {
		iter := func(i btree.Item) bool {
			entity := i.(*ResultEntity)
			if len(itervars) == 0 {
				results = append(results, entity)
				return i != tree.Max()
			}
			if subtree, found := entity.Next[variable]; found {
				_iterbtree(subtree, itervars[1:])
			} else {
				_iterbtree(entity.Next[itervars[0]], itervars[1:])
			}
			return i != tree.Max()
		}
		if tree == nil {
			return
		}
		tree.Ascend(iter)
	}
	tree := rm.vars[iterorder[0]]
	_iterbtree(tree, iterorder[1:])
	return results
}

// check the variable status.
// because variables can be nested, we need to figure out how to filter those out
func (rm *resultMap) getVar(variable string) *btree.BTree {
	if tree, found := rm.vars[variable]; found {
		return tree
	}
	return nil
}

func newResultMap() *resultMap {
	return &resultMap{
		vars:   make(map[string]*btree.BTree),
		tuples: btree.New(3),
	}
}

func (db *DB) expandTuples(rm *resultMap, selectVars []string) [][]turtle.URI {
	var tuples []map[string]turtle.URI
	var startvar string
	for v, state := range rm.varOrder.vars {
		if state == RESOLVED {
			startvar = v
			break
		}
	}
	tree := rm.vars[startvar]
	iter := func(i btree.Item) bool {
		entity := i.(*ResultEntity)
		newtups := db._getTuplesFromTree(startvar, entity)
		tuples = append(tuples, newtups...)
		return i != tree.Max()
	}
	tree.Ascend(iter)

	var results [][]turtle.URI
tupleLoop:
	for _, tup := range tuples {
		var row []turtle.URI
		for _, varname := range selectVars {
			if _, found := tup[varname]; !found {
				continue tupleLoop
			}
			row = append(row, tup[varname])
		}
		results = append(results, row)
	}
	return results
}

func (db *DB) _getTuplesFromTree(name string, ve *ResultEntity) []map[string]turtle.URI {
	uri := db.MustGetURI(ve.PK)
	var ret []map[string]turtle.URI
	if len(ve.Next) == 0 {
		ret = append(ret, map[string]turtle.URI{name: uri})
	} else {
		for lname, etree := range ve.Next {
			vars := make(map[string]turtle.URI)
			vars[name] = uri
			iter := func(i btree.Item) bool {
				entity := i.(*ResultEntity)
				for _, m := range db._getTuplesFromTree(lname, entity) {
					vars := make(map[string]turtle.URI)
					vars[name] = uri
					for k, v := range m {
						vars[k] = v
					}
					ret = append(ret, vars)
				}
				return i != etree.Max()
			}
			etree.Ascend(iter)
		}
	}
	return ret
}
<|endoftext|>


<commit_before>package data

// Evil things happen here. Rippled needs a V2 API...

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
)

type ledgerJSON Ledger

// adds all the legacy fields
type ledgerExtraJSON struct {
	ledgerJSON
	HumanCloseTime *RippleHumanTime `json:"close_time_human"`
	Hash           Hash256          `json:"hash"`
	LedgerHash     Hash256          `json:"ledger_hash"`
	TotalCoins     uint64           `json:"totalCoins,string"`
	SequenceNumber uint32           `json:"seqNum,string"`
}

func (l Ledger) MarshalJSON() ([]byte, error) {
	return json.Marshal(ledgerExtraJSON{
		ledgerJSON:     ledgerJSON(l),
		HumanCloseTime: l.CloseTime.Human(),
		Hash:           l.Hash(),
		LedgerHash:     l.Hash(),
		TotalCoins:     l.TotalXRP,
		SequenceNumber: l.LedgerSequence,
	})
}

func (l *Ledger) UnmarshalJSON(b []byte) error {
	var ledger ledgerExtraJSON
	if err := json.Unmarshal(b, &ledger); err != nil {
		return err
	}
	*l = Ledger(ledger.ledgerJSON)
	l.SetHash(ledger.Hash[:])
	return nil
}

// Wrapper type to enable second level of marshalling
// when found in ledger API call
type txmLedger struct {
	MetaData MetaData `json:"metaData"`
}

// Wrapper type to enable marshalling when found in
// AccountTx call
type splitTxm struct {
	Tx   json.RawMessage
	Meta json.RawMessage
}

// Wrapper types to enable second level of marshalling
// when found in tx API call
type txmNormal TransactionWithMetaData

var (
	txmTransactionTypeRegex = regexp.MustCompile(`"TransactionType"\s*:\s*"(\w+)"`)
	txmHashRegex            = regexp.MustCompile(`"hash"\s*:\s*"(\w+)"`)
	txmMetaTypeRegex        = regexp.MustCompile(`"(meta|metaData)"`)
	txmSplitTypeRegex       = regexp.MustCompile(`"tx":`)
)

// This function is a horrow show, demonstrating the huge
// inconsistencies in the presentation of a transaction
// by the rippled API
func (txm *TransactionWithMetaData) UnmarshalJSON(b []byte) error {
	if txmSplitTypeRegex.Match(b) {
		var split splitTxm
		if err := json.Unmarshal(b, &split); err != nil {
			return err
		}
		if err := json.Unmarshal(split.Tx, txm); err != nil {
			return err
		}
		return json.Unmarshal(split.Meta, &txm.MetaData)
	}
	txTypeMatch := txmTransactionTypeRegex.FindStringSubmatch(string(b))
	hashMatch := txmHashRegex.FindStringSubmatch(string(b))
	metaTypeMatch := txmMetaTypeRegex.FindStringSubmatch(string(b))
	var txType, hash, metaType string
	if txTypeMatch == nil {
		return fmt.Errorf("Not a valid transaction with metadata: Missing TransactionType")
	}
	txType = txTypeMatch[1]
	if hashMatch == nil {
		return fmt.Errorf("Not a valid transaction with metadata: Missing Hash")
	}
	hash = hashMatch[1]
	if metaTypeMatch != nil {
		metaType = metaTypeMatch[1]
	}
	txm.Transaction = GetTxFactoryByType(txType)()
	h, err := hex.DecodeString(hash)
	if err != nil {
		return fmt.Errorf("Bad hash: %s", hash)
	}
	txm.SetHash(h)
	if err := json.Unmarshal(b, txm.Transaction); err != nil {
		return err
	}
	switch metaType {
	case "meta":
		return json.Unmarshal(b, (*txmNormal)(txm))
	case "metaData":
		var meta txmLedger
		if err := json.Unmarshal(b, &meta); err != nil {
			return err
		}
		txm.MetaData = meta.MetaData
		return nil
	default:
		return nil
	}
}

func (txm TransactionWithMetaData) marshalJSON() ([]byte, []byte, error) {
	tx, err := json.Marshal(txm.Transaction)
	if err != nil {
		return nil, nil, err
	}
	meta, err := json.Marshal(txm.MetaData)
	if err != nil {
		return nil, nil, err
	}
	return tx, meta, nil
}

type extractTxm struct {
	Tx   json.RawMessage `json:"transaction"`
	Meta json.RawMessage `json:"meta"`
}

const extractTxmFormat = `%s,"meta":%s}`

func UnmarshalTransactionWithMetadata(b []byte, txm *TransactionWithMetaData) error {
	var extract extractTxm
	if err := json.Unmarshal(b, &extract); err != nil {
		return err
	}
	raw := fmt.Sprintf(extractTxmFormat, extract.Tx[:len(extract.Tx)-1], extract.Meta)
	return json.Unmarshal([]byte(raw), txm)
}

const txmFormat = `%s,"hash":"%s","inLedger":%d,"ledger_index":%d,"meta":%s}`

func (txm TransactionWithMetaData) MarshalJSON() ([]byte, error) {
	tx, meta, err := txm.marshalJSON()
	if err != nil {
		return nil, err
	}
	out := fmt.Sprintf(txmFormat, string(tx[:len(tx)-1]), txm.Hash().String(), txm.LedgerSequence, txm.LedgerSequence, string(meta))
	return []byte(out), nil
}

const txmSliceFormat = `%s,"hash":"%s","metaData":%s}`

func (s TransactionSlice) MarshalJSON() ([]byte, error) {
	raw := make([]json.RawMessage, len(s))
	var err error
	var tx, meta []byte
	for i, txm := range s {
		if tx, meta, err = txm.marshalJSON(); err != nil {
			return nil, err
		}
		extra := fmt.Sprintf(txmSliceFormat, string(tx[:len(tx)-1]), txm.Hash().String(), meta)
		raw[i] = json.RawMessage(extra)
	}
	return json.Marshal(raw)
}

var (
	leTypeRegex  = regexp.MustCompile(`"LedgerEntryType"\s*:\s*"(\w+)"`)
	leIndexRegex = regexp.MustCompile(`"index"\s*:\s*"(\w+)"`)
)

func (l *LedgerEntrySlice) UnmarshalJSON(b []byte) error {
	var s []json.RawMessage
	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}
	for _, raw := range s {
		leTypeMatch := leTypeRegex.FindStringSubmatch(string(raw))
		indexMatch := leIndexRegex.FindStringSubmatch(string(raw))
		if leTypeMatch == nil {
			return fmt.Errorf("Bad LedgerEntryType")
		}
		if indexMatch == nil {
			return fmt.Errorf("Missing LedgerEntry index")
		}
		le := GetLedgerEntryFactoryByType(leTypeMatch[1])()
		index, err := hex.DecodeString(indexMatch[1])
		if err != nil {
			return fmt.Errorf("Bad index: %s", index)
		}
		le.SetHash(index)
		if err := json.Unmarshal(raw, &le); err != nil {
			return err
		}
		*l = append(*l, le)
	}
	return nil
}

const leSliceFormat = `%s,"index":"%s"}`

func (s LedgerEntrySlice) MarshalJSON() ([]byte, error) {
	raw := make([]json.RawMessage, len(s))
	var err error
	for i, le := range s {
		if raw[i], err = json.Marshal(le); err != nil {
			return nil, err
		}
		extra := fmt.Sprintf(leSliceFormat, string(raw[i][:len(raw[i])-1]), le.Hash().String())
		raw[i] = json.RawMessage(extra)
	}
	return json.Marshal(raw)
}

func (i NodeIndex) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprintf("%016X", i)), nil
}

func (i *NodeIndex) UnmarshalText(b []byte) error {
	n, err := strconv.ParseUint(string(b), 16, 64)
	*i = NodeIndex(n)
	return err
}

func (r TransactionResult) MarshalText() ([]byte, error) {
	return []byte(r.String()), nil
}

func (r *TransactionResult) UnmarshalText(b []byte) error {
	if result, ok := reverseResults[string(b)]; ok {
		*r = result
		return nil
	}
	return fmt.Errorf("Unknown TransactionResult: %s", string(b))
}

func (l LedgerEntryType) MarshalText() ([]byte, error) {
	return []byte(ledgerEntryNames[l]), nil
}

func (l *LedgerEntryType) UnmarshalText(b []byte) error {
	if leType, ok := ledgerEntryTypes[string(b)]; ok {
		*l = leType
		return nil
	}
	return fmt.Errorf("Unknown LedgerEntryType: %s", string(b))
}

func (t TransactionType) MarshalText() ([]byte, error) {
	return []byte(txNames[t]), nil
}

func (t *TransactionType) UnmarshalText(b []byte) error {
	if txType, ok := txTypes[string(b)]; ok {
		*t = txType
		return nil
	}
	return fmt.Errorf("Unknown TransactionType: %s", string(b))
}

func (t RippleTime) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(t.Uint32()), 10)), nil
}

func (t *RippleTime) UnmarshalJSON(b []byte) error {
	n, err := strconv.ParseUint(string(b), 10, 32)
	if err != nil {
		return err
	}
	t.SetUint32(uint32(n))
	return nil
}

func (t RippleHumanTime) MarshalJSON() ([]byte, error) {
	return []byte(`"` + t.String() + `"`), nil
}

func (t *RippleHumanTime) UnmarshalJSON(b []byte) error {
	t.RippleTime = &RippleTime{}
	return t.SetString(string(b[1 : len(b)-1]))
}

func (v *Value) MarshalText() ([]byte, error) {
	if v.Native {
		return []byte(strconv.FormatUint(v.Num, 10)), nil
	}
	return []byte(v.String()), nil
}

func (v *Value) UnmarshalText(b []byte) error {
	value, err := NewValue(string(b), true)
	if err != nil {
		return err
	}
	*v = *value
	return nil
}

type nonNativeValue Value

func (v *nonNativeValue) UnmarshalText(b []byte) error {
	value, err := NewValue(string(b), false)
	if err != nil {
		return err
	}
	*v = nonNativeValue(*value)
	return nil
	// return (*Value)(v).Parse(string(b))
}

func (v *nonNativeValue) MarshalText() ([]byte, error) {
	return (*Value)(v).MarshalText()
}

type amountJSON struct {
	Value    *nonNativeValue `json:"value"`
	Currency Currency        `json:"currency"`
	Issuer   Account         `json:"issuer"`
}

func (a Amount) MarshalJSON() ([]byte, error) {
	if a.Native {
		return []byte(`"` + strconv.FormatUint(a.Num, 10) + `"`), nil
	}
	return json.Marshal(amountJSON{(*nonNativeValue)(a.Value), a.Currency, a.Issuer})
}

func (a *Amount) UnmarshalJSON(b []byte) (err error) {
	if b[0] != '{' {
		a.Value = new(Value)
		return json.Unmarshal(b, a.Value)
	}
	var dummy amountJSON
	if err := json.Unmarshal(b, &dummy); err != nil {
		return err
	}
	a.Value, a.Currency, a.Issuer = (*Value)(dummy.Value), dummy.Currency, dummy.Issuer
	return nil
}

func (c Currency) MarshalText() ([]byte, error) {
	return []byte(c.Machine()), nil
}

func (c *Currency) UnmarshalText(text []byte) error {
	var err error
	*c, err = NewCurrency(string(text))
	return err
}

func (h Hash128) MarshalText() ([]byte, error) {
	return b2h(h[:]), nil
}

func (h *Hash128) UnmarshalText(b []byte) error {
	_, err := hex.Decode(h[:], b)
	return err
}

func (h Hash160) MarshalText() ([]byte, error) {
	return b2h(h[:]), nil
}

func (h *Hash160) UnmarshalText(b []byte) error {
	_, err := hex.Decode(h[:], b)
	return err
}

func (h Hash256) MarshalText() ([]byte, error) {
	return b2h(h[:]), nil
}

func (h *Hash256) UnmarshalText(b []byte) error {
	_, err := hex.Decode(h[:], b)
	return err
}

func (a Account) MarshalText() ([]byte, error) {
	address, err := a.Hash()
	if err != nil {
		return nil, err
	}
	return address.MarshalText()
}

// Expects base58-encoded account id
func (a *Account) UnmarshalText(b []byte) error {
	account, err := NewAccountFromAddress(string(b))
	if err != nil {
		return err
	}
	copy(a[:], account[:])
	return nil
}

func (r RegularKey) MarshalText() ([]byte, error) {
	address, err := r.Hash()
	if err != nil {
		return nil, err
	}
	return address.MarshalText()
}

// Expects base58-encoded account id
func (r *RegularKey) UnmarshalText(b []byte) error {
	account, err := NewRegularKeyFromAddress(string(b))
	if err != nil {
		return err
	}
	copy(r[:], account[:])
	return nil
}

func (v VariableLength) MarshalText() ([]byte, error) {
	return b2h(v), nil
}

// Expects variable length hex
func (v *VariableLength) UnmarshalText(b []byte) error {
	var err error
	*v, err = hex.DecodeString(string(b))
	return err
}

func (p PublicKey) MarshalText() ([]byte, error) {
	return b2h(p[:]), nil
}

// Expects public key hex
func (p *PublicKey) UnmarshalText(b []byte) error {
	_, err := hex.Decode(p[:], b)
	return err
}
<commit_msg>Fix missing LedgerSequence in unmarshalled JSON<commit_after>package data

// Evil things happen here. Rippled needs a V2 API...

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
)

type ledgerJSON Ledger

// adds all the legacy fields
type ledgerExtraJSON struct {
	ledgerJSON
	HumanCloseTime *RippleHumanTime `json:"close_time_human"`
	Hash           Hash256          `json:"hash"`
	LedgerHash     Hash256          `json:"ledger_hash"`
	TotalCoins     uint64           `json:"totalCoins,string"`
	SequenceNumber uint32           `json:"seqNum,string"`
}

func (l Ledger) MarshalJSON() ([]byte, error) {
	return json.Marshal(ledgerExtraJSON{
		ledgerJSON:     ledgerJSON(l),
		HumanCloseTime: l.CloseTime.Human(),
		Hash:           l.Hash(),
		LedgerHash:     l.Hash(),
		TotalCoins:     l.TotalXRP,
		SequenceNumber: l.LedgerSequence,
	})
}

func (l *Ledger) UnmarshalJSON(b []byte) error {
	var ledger ledgerExtraJSON
	if err := json.Unmarshal(b, &ledger); err != nil {
		return err
	}
	*l = Ledger(ledger.ledgerJSON)
	l.SetHash(ledger.Hash[:])
	return nil
}

// Wrapper type to enable second level of marshalling
// when found in ledger API call
type txmLedger struct {
	MetaData MetaData `json:"metaData"`
}

// Wrapper type to enable marshalling when found in
// AccountTx call
type splitTxm struct {
	Tx   json.RawMessage
	Meta json.RawMessage
}

// Wrapper types to enable second level of marshalling
// when found in tx API call
type txmNormal TransactionWithMetaData

var (
	txmSplitTypeRegex       = regexp.MustCompile(`"tx":`)
	txmTransactionTypeRegex = regexp.MustCompile(`"TransactionType"\s*:\s*"(\w+)"`)
	txmHashRegex            = regexp.MustCompile(`"hash"\s*:\s*"(\w+)"`)
	txmMetaTypeRegex        = regexp.MustCompile(`"(meta|metaData)"`)
)

// This function is a horrow show, demonstrating the huge
// inconsistencies in the presentation of a transaction
// by the rippled API
func (txm *TransactionWithMetaData) UnmarshalJSON(b []byte) error {
	if txmSplitTypeRegex.Match(b) {
		var split splitTxm
		if err := json.Unmarshal(b, &split); err != nil {
			return err
		}
		if err := json.Unmarshal(split.Tx, txm); err != nil {
			return err
		}
		return json.Unmarshal(split.Meta, &txm.MetaData)
	}
	txTypeMatch := txmTransactionTypeRegex.FindStringSubmatch(string(b))
	hashMatch := txmHashRegex.FindStringSubmatch(string(b))
	metaTypeMatch := txmMetaTypeRegex.FindStringSubmatch(string(b))
	var txType, hash, metaType string
	if txTypeMatch == nil {
		return fmt.Errorf("Not a valid transaction with metadata: Missing TransactionType")
	}
	txType = txTypeMatch[1]
	if hashMatch == nil {
		return fmt.Errorf("Not a valid transaction with metadata: Missing Hash")
	}
	hash = hashMatch[1]
	if metaTypeMatch != nil {
		metaType = metaTypeMatch[1]
	}
	txm.Transaction = GetTxFactoryByType(txType)()
	h, err := hex.DecodeString(hash)
	if err != nil {
		return fmt.Errorf("Bad hash: %s", hash)
	}
	txm.SetHash(h)
	if err := json.Unmarshal(b, txm.Transaction); err != nil {
		return err
	}
	switch metaType {
	case "meta":
		return json.Unmarshal(b, (*txmNormal)(txm))
	case "metaData":
		var meta txmLedger
		if err := json.Unmarshal(b, &meta); err != nil {
			return err
		}
		txm.MetaData = meta.MetaData
		return nil
	default:
		return json.Unmarshal(b, (*txmNormal)(txm))
	}
}

func (txm TransactionWithMetaData) marshalJSON() ([]byte, []byte, error) {
	tx, err := json.Marshal(txm.Transaction)
	if err != nil {
		return nil, nil, err
	}
	meta, err := json.Marshal(txm.MetaData)
	if err != nil {
		return nil, nil, err
	}
	return tx, meta, nil
}

type extractTxm struct {
	Tx   json.RawMessage `json:"transaction"`
	Meta json.RawMessage `json:"meta"`
}

const extractTxmFormat = `%s,"meta":%s}`

func UnmarshalTransactionWithMetadata(b []byte, txm *TransactionWithMetaData) error {
	var extract extractTxm
	if err := json.Unmarshal(b, &extract); err != nil {
		return err
	}
	raw := fmt.Sprintf(extractTxmFormat, extract.Tx[:len(extract.Tx)-1], extract.Meta)
	return json.Unmarshal([]byte(raw), txm)
}

const txmFormat = `%s,"hash":"%s","inLedger":%d,"ledger_index":%d,"meta":%s}`

func (txm TransactionWithMetaData) MarshalJSON() ([]byte, error) {
	tx, meta, err := txm.marshalJSON()
	if err != nil {
		return nil, err
	}
	out := fmt.Sprintf(txmFormat, string(tx[:len(tx)-1]), txm.Hash().String(), txm.LedgerSequence, txm.LedgerSequence, string(meta))
	return []byte(out), nil
}

const txmSliceFormat = `%s,"hash":"%s","metaData":%s}`

func (s TransactionSlice) MarshalJSON() ([]byte, error) {
	raw := make([]json.RawMessage, len(s))
	var err error
	var tx, meta []byte
	for i, txm := range s {
		if tx, meta, err = txm.marshalJSON(); err != nil {
			return nil, err
		}
		extra := fmt.Sprintf(txmSliceFormat, string(tx[:len(tx)-1]), txm.Hash().String(), meta)
		raw[i] = json.RawMessage(extra)
	}
	return json.Marshal(raw)
}

var (
	leTypeRegex  = regexp.MustCompile(`"LedgerEntryType"\s*:\s*"(\w+)"`)
	leIndexRegex = regexp.MustCompile(`"index"\s*:\s*"(\w+)"`)
)

func (l *LedgerEntrySlice) UnmarshalJSON(b []byte) error {
	var s []json.RawMessage
	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}
	for _, raw := range s {
		leTypeMatch := leTypeRegex.FindStringSubmatch(string(raw))
		indexMatch := leIndexRegex.FindStringSubmatch(string(raw))
		if leTypeMatch == nil {
			return fmt.Errorf("Bad LedgerEntryType")
		}
		if indexMatch == nil {
			return fmt.Errorf("Missing LedgerEntry index")
		}
		le := GetLedgerEntryFactoryByType(leTypeMatch[1])()
		index, err := hex.DecodeString(indexMatch[1])
		if err != nil {
			return fmt.Errorf("Bad index: %s", index)
		}
		le.SetHash(index)
		if err := json.Unmarshal(raw, &le); err != nil {
			return err
		}
		*l = append(*l, le)
	}
	return nil
}

const leSliceFormat = `%s,"index":"%s"}`

func (s LedgerEntrySlice) MarshalJSON() ([]byte, error) {
	raw := make([]json.RawMessage, len(s))
	var err error
	for i, le := range s {
		if raw[i], err = json.Marshal(le); err != nil {
			return nil, err
		}
		extra := fmt.Sprintf(leSliceFormat, string(raw[i][:len(raw[i])-1]), le.Hash().String())
		raw[i] = json.RawMessage(extra)
	}
	return json.Marshal(raw)
}

func (i NodeIndex) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprintf("%016X", i)), nil
}

func (i *NodeIndex) UnmarshalText(b []byte) error {
	n, err := strconv.ParseUint(string(b), 16, 64)
	*i = NodeIndex(n)
	return err
}

func (r TransactionResult) MarshalText() ([]byte, error) {
	return []byte(r.String()), nil
}

func (r *TransactionResult) UnmarshalText(b []byte) error {
	if result, ok := reverseResults[string(b)]; ok {
		*r = result
		return nil
	}
	return fmt.Errorf("Unknown TransactionResult: %s", string(b))
}

func (l LedgerEntryType) MarshalText() ([]byte, error) {
	return []byte(ledgerEntryNames[l]), nil
}

func (l *LedgerEntryType) UnmarshalText(b []byte) error {
	if leType, ok := ledgerEntryTypes[string(b)]; ok {
		*l = leType
		return nil
	}
	return fmt.Errorf("Unknown LedgerEntryType: %s", string(b))
}

func (t TransactionType) MarshalText() ([]byte, error) {
	return []byte(txNames[t]), nil
}

func (t *TransactionType) UnmarshalText(b []byte) error {
	if txType, ok := txTypes[string(b)]; ok {
		*t = txType
		return nil
	}
	return fmt.Errorf("Unknown TransactionType: %s", string(b))
}

func (t RippleTime) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(t.Uint32()), 10)), nil
}

func (t *RippleTime) UnmarshalJSON(b []byte) error {
	n, err := strconv.ParseUint(string(b), 10, 32)
	if err != nil {
		return err
	}
	t.SetUint32(uint32(n))
	return nil
}

func (t RippleHumanTime) MarshalJSON() ([]byte, error) {
	return []byte(`"` + t.String() + `"`), nil
}

func (t *RippleHumanTime) UnmarshalJSON(b []byte) error {
	t.RippleTime = &RippleTime{}
	return t.SetString(string(b[1 : len(b)-1]))
}

func (v *Value) MarshalText() ([]byte, error) {
	if v.Native {
		return []byte(strconv.FormatUint(v.Num, 10)), nil
	}
	return []byte(v.String()), nil
}

func (v *Value) UnmarshalText(b []byte) error {
	value, err := NewValue(string(b), true)
	if err != nil {
		return err
	}
	*v = *value
	return nil
}

type nonNativeValue Value

func (v *nonNativeValue) UnmarshalText(b []byte) error {
	value, err := NewValue(string(b), false)
	if err != nil {
		return err
	}
	*v = nonNativeValue(*value)
	return nil
	// return (*Value)(v).Parse(string(b))
}

func (v *nonNativeValue) MarshalText() ([]byte, error) {
	return (*Value)(v).MarshalText()
}

type amountJSON struct {
	Value    *nonNativeValue `json:"value"`
	Currency Currency        `json:"currency"`
	Issuer   Account         `json:"issuer"`
}

func (a Amount) MarshalJSON() ([]byte, error) {
	if a.Native {
		return []byte(`"` + strconv.FormatUint(a.Num, 10) + `"`), nil
	}
	return json.Marshal(amountJSON{(*nonNativeValue)(a.Value), a.Currency, a.Issuer})
}

func (a *Amount) UnmarshalJSON(b []byte) (err error) {
	if b[0] != '{' {
		a.Value = new(Value)
		return json.Unmarshal(b, a.Value)
	}
	var dummy amountJSON
	if err := json.Unmarshal(b, &dummy); err != nil {
		return err
	}
	a.Value, a.Currency, a.Issuer = (*Value)(dummy.Value), dummy.Currency, dummy.Issuer
	return nil
}

func (c Currency) MarshalText() ([]byte, error) {
	return []byte(c.Machine()), nil
}

func (c *Currency) UnmarshalText(text []byte) error {
	var err error
	*c, err = NewCurrency(string(text))
	return err
}

func (h Hash128) MarshalText() ([]byte, error) {
	return b2h(h[:]), nil
}

func (h *Hash128) UnmarshalText(b []byte) error {
	_, err := hex.Decode(h[:], b)
	return err
}

func (h Hash160) MarshalText() ([]byte, error) {
	return b2h(h[:]), nil
}

func (h *Hash160) UnmarshalText(b []byte) error {
	_, err := hex.Decode(h[:], b)
	return err
}

func (h Hash256) MarshalText() ([]byte, error) {
	return b2h(h[:]), nil
}

func (h *Hash256) UnmarshalText(b []byte) error {
	_, err := hex.Decode(h[:], b)
	return err
}

func (a Account) MarshalText() ([]byte, error) {
	address, err := a.Hash()
	if err != nil {
		return nil, err
	}
	return address.MarshalText()
}

// Expects base58-encoded account id
func (a *Account) UnmarshalText(b []byte) error {
	account, err := NewAccountFromAddress(string(b))
	if err != nil {
		return err
	}
	copy(a[:], account[:])
	return nil
}

func (r RegularKey) MarshalText() ([]byte, error) {
	address, err := r.Hash()
	if err != nil {
		return nil, err
	}
	return address.MarshalText()
}

// Expects base58-encoded account id
func (r *RegularKey) UnmarshalText(b []byte) error {
	account, err := NewRegularKeyFromAddress(string(b))
	if err != nil {
		return err
	}
	copy(r[:], account[:])
	return nil
}

func (v VariableLength) MarshalText() ([]byte, error) {
	return b2h(v), nil
}

// Expects variable length hex
func (v *VariableLength) UnmarshalText(b []byte) error {
	var err error
	*v, err = hex.DecodeString(string(b))
	return err
}

func (p PublicKey) MarshalText() ([]byte, error) {
	return b2h(p[:]), nil
}

// Expects public key hex
func (p *PublicKey) UnmarshalText(b []byte) error {
	_, err := hex.Decode(p[:], b)
	return err
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative 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 filesystem

import (
	"errors"
	"fmt"
	"github.com/google/go-cmp/cmp"
	"github.com/knative/eventing/pkg/sidecar/configmap"
	"github.com/knative/eventing/pkg/sidecar/fanout"
	"github.com/knative/eventing/pkg/sidecar/multichannelfanout"
	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
	"go.uber.org/zap"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"strings"
	"sync"
	"testing"
	"time"
)

func TestReadConfigMap(t *testing.T) {
	testCases := []struct {
		name        string
		createDir   bool
		config      string
		expected    *multichannelfanout.Config
		expectedErr bool
	}{
		{
			name:      "dir does not exist",
			createDir: false,
		},
		{
			name:        "no data",
			createDir:   true,
			expectedErr: true,
		},
		{
			name:      "invalid YAML",
			createDir: true,
			config: `
				key:
				  - value
				 - different indent level
				`,
			expectedErr: true,
		},
		{
			name:        "valid YAML -- invalid JSON",
			config:      "{ nil: Key }",
			createDir:   true,
			expectedErr: true,
		},
		{
			name:        "unknown field",
			config:      "{ channelConfigs: [ { not: a-defined-field } ] }",
			createDir:   true,
			expectedErr: true,
		},
		{
			name:      "valid",
			createDir: true,
			config: `
				channelConfigs:
				  - namespace: default
					name: c1
					fanoutConfig:
					  subscriptions:
						- callableDomain: event-changer.default.svc.cluster.local
						  sinkableDomain: message-dumper-bar.default.svc.cluster.local
						- callableDomain: message-dumper-foo.default.svc.cluster.local
						- sinkableDomain: message-dumper-bar.default.svc.cluster.local
				  - namespace: default
					name: c2
					fanoutConfig:
					  subscriptions:
						- sinkableDomain: message-dumper-foo.default.svc.cluster.local
				  - namespace: other
					name: c3
					fanoutConfig:
					  subscriptions:
						- sinkableDomain: message-dumper-foo.default.svc.cluster.local
				`,
			expected: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									CallableDomain: "event-changer.default.svc.cluster.local",
									SinkableDomain: "message-dumper-bar.default.svc.cluster.local",
								},
								{
									CallableDomain: "message-dumper-foo.default.svc.cluster.local",
								},
								{
									SinkableDomain: "message-dumper-bar.default.svc.cluster.local",
								},
							},
						},
					},
					{
						Namespace: "default",
						Name:      "c2",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "message-dumper-foo.default.svc.cluster.local",
								},
							},
						},
					},
					{
						Namespace: "other",
						Name:      "c3",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "message-dumper-foo.default.svc.cluster.local",
								},
							},
						},
					},
				},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var dir string
			if tc.createDir {
				dir = createTempDir(t)
				defer os.RemoveAll(dir)
			} else {
				dir = "/tmp/doesNotExist"
			}
			writeConfigString(t, dir, tc.config)
			c, e := readConfigMap(zap.NewNop(), dir)
			if tc.expectedErr {
				if e == nil {
					t.Errorf("Expected an error, actual nil")
				}
				return
			}
			if !cmp.Equal(c, tc.expected) {
				t.Errorf("Unexpected config. Expected '%v'. Actual '%v'.", tc.expected, c)
			}
		})
	}
}

func TestWatch(t *testing.T) {
	testCases := map[string]struct {
		initialConfigErr error
		initialConfig    *multichannelfanout.Config
		updateConfigErr  error
		updateConfig     *multichannelfanout.Config
	}{
		"error applying initial config": {
			initialConfig:    &multichannelfanout.Config{},
			initialConfigErr: errors.New("test-induced error"),
		},
		"read initial config": {
			initialConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "foo.bar",
								},
							},
						},
					},
				},
			},
		},
		"error apply updated config": {
			initialConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "foo.bar",
								},
							},
						},
					},
				},
			},
			updateConfigErr: errors.New("test-induced error"),
		},
		"update config": {
			initialConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "foo.bar",
								},
							},
						},
					},
				},
			},
			updateConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "new-channel",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									CallableDomain: "baz.qux",
								},
							},
						},
					},
				},
			},
		},
	}
	for n, tc := range testCases {
		t.Run(n, func(t *testing.T) {
			dir := createTempDir(t)
			defer os.RemoveAll(dir)
			writeConfig(t, dir, tc.initialConfig)

			cuc := &configUpdatedChecker{
				updateConfigErr: tc.initialConfigErr,
			}
			cmw, err := NewConfigMapWatcher(zap.NewNop(), dir, cuc.updateConfig)
			if err != nil {
				if tc.initialConfigErr != err {
					t.Errorf("Unexpected error making ConfigMapWatcher. Expected: '%v'. Actual '%v'", tc.initialConfigErr, err)
				}
				return
			}
			ac := cuc.getConfig()
			if !cmp.Equal(tc.initialConfig, ac) {
				t.Errorf("Unexpected initial config. Expected '%v'. Actual '%v'", tc.initialConfig, ac)
			}

			stopCh := make(chan struct{})
			go cmw.Start(stopCh)
			defer func() {
				close(stopCh)
			}()
			// Sadly, the test is flaky unless we sleep here, waiting for the file system
			// watcher to truly start.
			time.Sleep(100 * time.Millisecond)

			if tc.updateConfigErr != nil {
				cuc.updateConfigErr = tc.updateConfigErr
			}

			expected := tc.initialConfig
			if tc.updateConfig != nil {
				expected = tc.updateConfig
			}

			cuc.updateCalled = make(chan struct{}, 1)
			writeConfig(t, dir, expected)
			// The watcher is running in another goroutine, give it some time to notice the
			// change.
			select {
			case <-cuc.updateCalled:
				break
			case <-time.After(5 * time.Second):
				t.Errorf("Time out waiting for watcher to notice change.")
			}

			ac = cuc.getConfig()
			if !cmp.Equal(ac, expected) {
				t.Errorf("Unexpected update config. Expected '%v'. Actual '%v'", expected, ac)
			}
		})
	}
}

type configUpdatedChecker struct {
	configLock      sync.Mutex
	config          *multichannelfanout.Config
	updateCalled    chan struct{}
	updateConfigErr error
}

func (cuc *configUpdatedChecker) updateConfig(config *multichannelfanout.Config) error {
	cuc.configLock.Lock()
	defer cuc.configLock.Unlock()
	cuc.config = config
	if cuc.updateCalled != nil {
		cuc.updateCalled <- struct{}{}
	}
	return cuc.updateConfigErr
}

func (cuc *configUpdatedChecker) getConfig() *multichannelfanout.Config {
	cuc.configLock.Lock()
	defer cuc.configLock.Unlock()
	return cuc.config
}

func createTempDir(t *testing.T) string {
	dir, err := ioutil.TempDir("", "configMapHandlerTest")
	if err != nil {
		t.Errorf("Unable to make temp directory: %v", err)
	}
	return dir
}

func writeConfig(t *testing.T, dir string, config *multichannelfanout.Config) {
	if config != nil {
		yb, err := yaml.Marshal(config)
		if err != nil {
			t.Errorf("Unable to marshal the config")
		}
		writeConfigString(t, dir, string(yb))
	}
}

func writeConfigString(t *testing.T, dir, config string) {
	if config != "" {
		// Golang editors tend to replace leading spaces with tabs. YAML is left whitespace
		// sensitive, so let's replace the tabs with spaces.
		leftSpaceConfig := strings.Replace(config, "\t", "    ", -1)
		err := ioutil.WriteFile(fmt.Sprintf("%s/%s", dir, configmap.MultiChannelFanoutConfigKey), []byte(leftSpaceConfig), 0700)
		if err != nil {
			t.Errorf("Problem writing the config file: %v", err)
		}
	}
}
<commit_msg>Atomically write the file, rather than opening with truncate. (#558)<commit_after>/*
Copyright 2018 The Knative 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 filesystem

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/knative/eventing/pkg/sidecar/configmap"
	"github.com/knative/eventing/pkg/sidecar/fanout"
	"github.com/knative/eventing/pkg/sidecar/multichannelfanout"
	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
	"go.uber.org/zap"
	yaml "gopkg.in/yaml.v2"
)

func TestReadConfigMap(t *testing.T) {
	testCases := []struct {
		name        string
		createDir   bool
		config      string
		expected    *multichannelfanout.Config
		expectedErr bool
	}{
		{
			name:      "dir does not exist",
			createDir: false,
		},
		{
			name:        "no data",
			createDir:   true,
			expectedErr: true,
		},
		{
			name:      "invalid YAML",
			createDir: true,
			config: `
				key:
				  - value
				 - different indent level
				`,
			expectedErr: true,
		},
		{
			name:        "valid YAML -- invalid JSON",
			config:      "{ nil: Key }",
			createDir:   true,
			expectedErr: true,
		},
		{
			name:        "unknown field",
			config:      "{ channelConfigs: [ { not: a-defined-field } ] }",
			createDir:   true,
			expectedErr: true,
		},
		{
			name:      "valid",
			createDir: true,
			config: `
				channelConfigs:
				  - namespace: default
					name: c1
					fanoutConfig:
					  subscriptions:
						- callableDomain: event-changer.default.svc.cluster.local
						  sinkableDomain: message-dumper-bar.default.svc.cluster.local
						- callableDomain: message-dumper-foo.default.svc.cluster.local
						- sinkableDomain: message-dumper-bar.default.svc.cluster.local
				  - namespace: default
					name: c2
					fanoutConfig:
					  subscriptions:
						- sinkableDomain: message-dumper-foo.default.svc.cluster.local
				  - namespace: other
					name: c3
					fanoutConfig:
					  subscriptions:
						- sinkableDomain: message-dumper-foo.default.svc.cluster.local
				`,
			expected: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									CallableDomain: "event-changer.default.svc.cluster.local",
									SinkableDomain: "message-dumper-bar.default.svc.cluster.local",
								},
								{
									CallableDomain: "message-dumper-foo.default.svc.cluster.local",
								},
								{
									SinkableDomain: "message-dumper-bar.default.svc.cluster.local",
								},
							},
						},
					},
					{
						Namespace: "default",
						Name:      "c2",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "message-dumper-foo.default.svc.cluster.local",
								},
							},
						},
					},
					{
						Namespace: "other",
						Name:      "c3",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "message-dumper-foo.default.svc.cluster.local",
								},
							},
						},
					},
				},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var dir string
			if tc.createDir {
				dir = createTempDir(t)
				defer os.RemoveAll(dir)
			} else {
				dir = "/tmp/doesNotExist"
			}
			writeConfigString(t, dir, tc.config)
			c, e := readConfigMap(zap.NewNop(), dir)
			if tc.expectedErr {
				if e == nil {
					t.Errorf("Expected an error, actual nil")
				}
				return
			}
			if !cmp.Equal(c, tc.expected) {
				t.Errorf("Unexpected config. Expected '%v'. Actual '%v'.", tc.expected, c)
			}
		})
	}
}

func TestWatch(t *testing.T) {
	testCases := map[string]struct {
		initialConfigErr error
		initialConfig    *multichannelfanout.Config
		updateConfigErr  error
		updateConfig     *multichannelfanout.Config
	}{
		"error applying initial config": {
			initialConfig:    &multichannelfanout.Config{},
			initialConfigErr: errors.New("test-induced error"),
		},
		"read initial config": {
			initialConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "foo.bar",
								},
							},
						},
					},
				},
			},
		},
		"error apply updated config": {
			initialConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "foo.bar",
								},
							},
						},
					},
				},
			},
			updateConfigErr: errors.New("test-induced error"),
		},
		"update config": {
			initialConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "c1",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									SinkableDomain: "foo.bar",
								},
							},
						},
					},
				},
			},
			updateConfig: &multichannelfanout.Config{
				ChannelConfigs: []multichannelfanout.ChannelConfig{
					{
						Namespace: "default",
						Name:      "new-channel",
						FanoutConfig: fanout.Config{
							Subscriptions: []duckv1alpha1.ChannelSubscriberSpec{
								{
									CallableDomain: "baz.qux",
								},
							},
						},
					},
				},
			},
		},
	}
	for n, tc := range testCases {
		t.Run(n, func(t *testing.T) {
			dir := createTempDir(t)
			defer os.RemoveAll(dir)
			writeConfig(t, dir, tc.initialConfig)

			cuc := &configUpdatedChecker{
				updateConfigErr: tc.initialConfigErr,
			}
			cmw, err := NewConfigMapWatcher(zap.NewNop(), dir, cuc.updateConfig)
			if err != nil {
				if tc.initialConfigErr != err {
					t.Errorf("Unexpected error making ConfigMapWatcher. Expected: '%v'. Actual '%v'", tc.initialConfigErr, err)
				}
				return
			}
			ac := cuc.getConfig()
			if !cmp.Equal(tc.initialConfig, ac) {
				t.Errorf("Unexpected initial config. Expected '%v'. Actual '%v'", tc.initialConfig, ac)
			}

			stopCh := make(chan struct{})
			go cmw.Start(stopCh)
			defer func() {
				close(stopCh)
			}()
			// Sadly, the test is flaky unless we sleep here, waiting for the file system
			// watcher to truly start.
			time.Sleep(100 * time.Millisecond)

			if tc.updateConfigErr != nil {
				cuc.updateConfigErr = tc.updateConfigErr
			}

			expected := tc.initialConfig
			if tc.updateConfig != nil {
				expected = tc.updateConfig
			}

			cuc.updateCalled = make(chan struct{}, 1)
			writeConfig(t, dir, expected)
			// The watcher is running in another goroutine, give it some time to notice the
			// change.
			select {
			case <-cuc.updateCalled:
				break
			case <-time.After(5 * time.Second):
				t.Errorf("Time out waiting for watcher to notice change.")
			}

			ac = cuc.getConfig()
			if !cmp.Equal(ac, expected) {
				t.Errorf("Unexpected update config. Expected '%v'. Actual '%v'", expected, ac)
			}
		})
	}
}

type configUpdatedChecker struct {
	configLock      sync.Mutex
	config          *multichannelfanout.Config
	updateCalled    chan struct{}
	updateConfigErr error
}

func (cuc *configUpdatedChecker) updateConfig(config *multichannelfanout.Config) error {
	cuc.configLock.Lock()
	defer cuc.configLock.Unlock()
	cuc.config = config
	if cuc.updateCalled != nil {
		cuc.updateCalled <- struct{}{}
	}
	return cuc.updateConfigErr
}

func (cuc *configUpdatedChecker) getConfig() *multichannelfanout.Config {
	cuc.configLock.Lock()
	defer cuc.configLock.Unlock()
	return cuc.config
}

func createTempDir(t *testing.T) string {
	dir, err := ioutil.TempDir("", "configMapHandlerTest")
	if err != nil {
		t.Errorf("Unable to make temp directory: %v", err)
	}
	return dir
}

func writeConfig(t *testing.T, dir string, config *multichannelfanout.Config) {
	if config != nil {
		yb, err := yaml.Marshal(config)
		if err != nil {
			t.Errorf("Unable to marshal the config")
		}
		writeConfigString(t, dir, string(yb))
	}
}

func writeConfigString(t *testing.T, dir, config string) {
	if config != "" {
		// Golang editors tend to replace leading spaces with tabs. YAML is left whitespace
		// sensitive, so let's replace the tabs with spaces.
		leftSpaceConfig := strings.Replace(config, "\t", "    ", -1)
		err := atomicWriteFile(t, fmt.Sprintf("%s/%s", dir, configmap.MultiChannelFanoutConfigKey), []byte(leftSpaceConfig), 0700)
		if err != nil {
			t.Errorf("Problem writing the config file: %v", err)
		}
	}
}

func atomicWriteFile(t *testing.T, file string, bytes []byte, perm os.FileMode) error {
	// In order to more closely replicate how K8s writes ConfigMaps to the file system, we will
	// atomically swap out the file by writing it to a temp directory, then renaming it into the
	// directory we are watching.
	tempDir := createTempDir(t)
	defer os.RemoveAll(tempDir)

	tempFile := fmt.Sprintf("%s/%s", tempDir, "temp")
	err := ioutil.WriteFile(tempFile, bytes, perm)
	if err != nil {
		return err
	}
	return os.Rename(tempFile, file)
}
<|endoftext|>


<commit_before>package client

import (
	"errors"
	"strings"
)

// lexMsg scans a IRC message and outputs its tokens.
func lexMsg(message string) (prefix, command string, params []string, err error) {

	// grab prefix if present
	prefixEnd := -1
	if strings.HasPrefix(message, ":") {
		prefixEnd = strings.Index(message, " ")
		if prefixEnd == -1 {
			err = errors.New("Message with only a prefix")
			return
		}
		prefix = message[1:prefixEnd]
	}

	// grab trailing param if present
	var trailing string
	trailingStart := strings.Index(message, " :")
	if trailingStart >= 0 {
		trailing = message[trailingStart+2:]
	} else {
		trailingStart = len(message)
	}

	tmp := message[prefixEnd+1 : trailingStart]
	cmdAndParams := strings.Fields(tmp)
	if len(cmdAndParams) < 1 {
		err = errors.New("Cannot lex command")
		return
	}

	command = cmdAndParams[0]
	params = cmdAndParams[1:]
	if trailing != "" {
		params = append(params, trailing)
	}

	return
}

// ParseServerMsg parses an IRC message from an IRC server and outputs
// a string ready to be printed out from the client.
func ParseServerMsg(message string) (output, context string, err error) {
	return "", "", nil
}

func join(s string) (prefix, params) {
	nick := resolveNick(prefix)
}

func quit(prefix string, params []string) (output, context string) {
	// TODO
	return "", ""
}

// resolveNick returns the nick or hostname associated with the IRC message
// it returns empty string when a nick cannot be resolved
func resolveNick(prefix string) (nick string, err error) {
	ind := strings.Index(prefix, "!")
	if ind == -1 {
		err = errors.New("Cannot resolve nick")
		return
	}
	nick = prefix[:ind]
	return
}
<commit_msg>Parser switch<commit_after>package client

import (
	"errors"
	"strings"
)

// lexMsg scans a IRC message and outputs its tokens.
func lexMsg(message string) (prefix, command string, params []string, err error) {

	// grab prefix if present
	prefixEnd := -1
	if strings.HasPrefix(message, ":") {
		prefixEnd = strings.Index(message, " ")
		if prefixEnd == -1 {
			err = errors.New("Message with only a prefix")
			return
		}
		prefix = message[1:prefixEnd]
	}

	// grab trailing param if present
	var trailing string
	trailingStart := strings.Index(message, " :")
	if trailingStart >= 0 {
		trailing = message[trailingStart+2:]
	} else {
		trailingStart = len(message)
	}

	tmp := message[prefixEnd+1 : trailingStart]
	cmdAndParams := strings.Fields(tmp)
	if len(cmdAndParams) < 1 {
		err = errors.New("Cannot lex command")
		return
	}

	command = cmdAndParams[0]
	params = cmdAndParams[1:]
	if trailing != "" {
		params = append(params, trailing)
	}

	return
}

// ParseServerMsg parses an IRC message from an IRC server and outputs
// a string ready to be printed out from the client.
func ParseServerMsg(message string) (output, context string, err error) {
	prefix, command, params, err := lexMsg(message)
	if err != nil {
		return
	}
	switch command {
	case "JOIN":
		return join(prefix, params)
	case "QUIT":
		return quit(prefix, params)
	default:
		err = errors.New("Unknown command.")
		return
	}
}

func join(prefix string, params []string) (output, context string, err error) {
	return
}

func quit(prefix string, params []string) (output, context string, err error) {
	nick, err := resolveNick(prefix)
	if err != nil {
		return
	}
	_ = nick
	return "", "", nil
}

// resolveNick returns the nick or hostname associated with the IRC message
// it returns empty string when a nick cannot be resolved
func resolveNick(prefix string) (nick string, err error) {
	ind := strings.Index(prefix, "!")
	if ind == -1 {
		err = errors.New("Cannot resolve nick")
		return
	}
	nick = prefix[:ind]
	return
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/miekg/dns"
	"testing"
	"time"
)

const TEST_ADDR = "127.0.0.1:9953"

func TestDNSResponse(t *testing.T) {
	config := NewConfig()
	config.dnsAddr = TEST_ADDR

	server := NewDNSServer(config)
	go server.Start()

	// Allow some time for server to start
	time.Sleep(250 * time.Millisecond)

	m := new(dns.Msg)
	m.Id = dns.Id()
	m.RecursionDesired = true
	m.Question = []dns.Question{
		dns.Question{"google.com.", dns.TypeA, dns.ClassINET},
	}
	c := new(dns.Client)
	in, _, err := c.Exchange(m, TEST_ADDR)

	if err != nil {
		t.Error("Error response from the server", err)
	}

	if len(in.Answer) < 3 {
		t.Error("DNS request only responded ", len(in.Answer), "answers")
	}

	server.Stop()

	c = new(dns.Client)
	_, _, err = c.Exchange(m, TEST_ADDR)

	if err == nil {
		t.Error("Server still running but should be shut down.")
	}
}

func TestServiceManagement(t *testing.T) {
	list := ServiceListProvider(NewDNSServer(NewConfig()))

	if len(list.GetAllServices()) != 0 {
		t.Error("Initial service count should be 0.")
	}

	A := Service{Name: "bar"}
	list.AddService("foo", A)

	if len(list.GetAllServices()) != 1 {
		t.Error("Service count should be 1.")
	}

	A.Name = "baz"

	s1, err := list.GetService("foo")
	if err != nil {
		t.Error("GetService error", err)
	}

	if s1.Name != "bar" {
		t.Error("Expected: bar got:", s1.Name)
	}

	_, err = list.GetService("boo")

	if err == nil {
		t.Error("Request to boo should have failed")
	}

	list.AddService("boo", Service{Name: "boo"})

	all := list.GetAllServices()

	delete(all, "foo")
	s2 := all["boo"]
	s2.Name = "zoo"

	if len(list.GetAllServices()) != 2 {
		t.Error("Local map change should not remove items")
	}

	if s1, _ = list.GetService("boo"); s1.Name != "boo" {
		t.Error("Local map change should not change items")
	}

	err = list.RemoveService("bar")
	if err == nil {
		t.Error("Removing bar should fail")
	}

	err = list.RemoveService("foo")
	if err != nil {
		t.Error("Removing foo failed", err)
	}

	if len(list.GetAllServices()) != 1 {
		t.Error("Item count after remove should be 1")
	}

}
<commit_msg>Remove DNS stop test<commit_after>package main

import (
	"github.com/miekg/dns"
	"testing"
	"time"
)

const TEST_ADDR = "127.0.0.1:9953"

func TestDNSResponse(t *testing.T) {
	config := NewConfig()
	config.dnsAddr = TEST_ADDR

	server := NewDNSServer(config)
	go server.Start()

	// Allow some time for server to start
	time.Sleep(250 * time.Millisecond)

	m := new(dns.Msg)
	m.Id = dns.Id()
	m.RecursionDesired = true
	m.Question = []dns.Question{
		dns.Question{"google.com.", dns.TypeA, dns.ClassINET},
	}
	c := new(dns.Client)
	in, _, err := c.Exchange(m, TEST_ADDR)

	if err != nil {
		t.Error("Error response from the server", err)
	}

	if len(in.Answer) < 3 {
		t.Error("DNS request only responded ", len(in.Answer), "answers")
	}

	// // This test is slow and pointless
	// server.Stop()
	//
	// c = new(dns.Client)
	// _, _, err = c.Exchange(m, TEST_ADDR)
	//
	// if err == nil {
	// 	t.Error("Server still running but should be shut down.")
	// }
}

func TestServiceManagement(t *testing.T) {
	list := ServiceListProvider(NewDNSServer(NewConfig()))

	if len(list.GetAllServices()) != 0 {
		t.Error("Initial service count should be 0.")
	}

	A := Service{Name: "bar"}
	list.AddService("foo", A)

	if len(list.GetAllServices()) != 1 {
		t.Error("Service count should be 1.")
	}

	A.Name = "baz"

	s1, err := list.GetService("foo")
	if err != nil {
		t.Error("GetService error", err)
	}

	if s1.Name != "bar" {
		t.Error("Expected: bar got:", s1.Name)
	}

	_, err = list.GetService("boo")

	if err == nil {
		t.Error("Request to boo should have failed")
	}

	list.AddService("boo", Service{Name: "boo"})

	all := list.GetAllServices()

	delete(all, "foo")
	s2 := all["boo"]
	s2.Name = "zoo"

	if len(list.GetAllServices()) != 2 {
		t.Error("Local map change should not remove items")
	}

	if s1, _ = list.GetService("boo"); s1.Name != "boo" {
		t.Error("Local map change should not change items")
	}

	err = list.RemoveService("bar")
	if err == nil {
		t.Error("Removing bar should fail")
	}

	err = list.RemoveService("foo")
	if err != nil {
		t.Error("Removing foo failed", err)
	}

	if len(list.GetAllServices()) != 1 {
		t.Error("Item count after remove should be 1")
	}

}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Tigera, Inc. All rights reserved.

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

// Test cases (Pool object e2e):
// Test 1: Pass two fully populated PoolSpecs and expect the series of operations to succeed.
// Test 2: Pass one partially populated PoolSpec and another fully populated PoolSpec and expect the series of operations to succeed.
// Test 3: Pass one fully populated PoolSpec and another empty PoolSpec and expect the series of operations to succeed.
// Test 4: Pass two fully populated PoolSpecs with two PoolMetadata (one IPv4 and another IPv6) and expect the series of operations to succeed.

// Series of operations each test goes through:
// Update meta1 - check for failure (because it doesn't exist).
// Create meta1 with spec1.
// Apply meta2 with spec2.
// Get meta1 and meta2, compare spec1 and spec2.
// Update meta1 with spec2.
// Get meta1 compare spec2.
// List (empty Meta) ... Get meta1 and meta2.
// List (using Meta1) ... Get meta1.
// Delete meta1.
// Get meta1 ... fail.
// Delete meta2.
// List (empty Meta) ... Get no entries (should not error).

package client_test

import (
	"errors"
	"log"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"github.com/projectcalico/libcalico-go/lib/api"
	"github.com/projectcalico/libcalico-go/lib/testutils"
)

var _ = Describe("Pool tests", func() {

	DescribeTable("Pool e2e tests",
		func(meta1, meta2 api.PoolMetadata, spec1, spec2 api.PoolSpec) {

			// Erase etcd clean.
			testutils.CleanEtcd()

			// Create a new client.
			c, err := testutils.NewClient("")
			if err != nil {
				log.Println("Error creating client:", err)
			}
			By("Updating the pool before it is created")
			_, outError := c.Pools().Update(&api.Pool{Metadata: meta1, Spec: spec1})

			// Should return an error.
			Expect(outError.Error()).To(Equal(errors.New("resource does not exist: Pool(cidr=10.0.0.0/24)").Error()))

			By("Create, Apply, Get and compare")

			// Create a pool with meta1 and spec1.
			_, outError = c.Pools().Create(&api.Pool{Metadata: meta1, Spec: spec1})

			// Apply a pool with meta2 and spec2.
			_, outError = c.Pools().Apply(&api.Pool{Metadata: meta2, Spec: spec2})

			// Get pool with meta1.
			outPool1, outError1 := c.Pools().Get(meta1)
			log.Println("Out Pool object: ", outPool1)

			// Get pool with meta2.
			outPool2, outError2 := c.Pools().Get(meta2)
			log.Println("Out Pool object: ", outPool2)

			// Should match spec1 & outPool1 and outPool2 & spec2 and errors to be nil.
			Expect(outPool1.Spec).To(Equal(spec1))
			Expect(outPool2.Spec).To(Equal(spec2))
			Expect(outError1).NotTo(HaveOccurred())
			Expect(outError2).NotTo(HaveOccurred())

			By("Update, Get and compare")

			// Update meta1 pool with spec2.
			c.Pools().Update(&api.Pool{Metadata: meta1, Spec: spec2})

			// Get pool with meta1.
			outPool1, outError1 = c.Pools().Get(meta1)

			// Assert the Spec for pool with meta1 matches spec2 and no error.
			Expect(outPool1.Spec).To(Equal(spec2))
			Expect(outError1).NotTo(HaveOccurred())

			By("List all the pools and compare")

			// Get a list of pools.
			poolList, outError := c.Pools().List(api.PoolMetadata{})
			log.Println("Get pool list returns: ", poolList.Items)
			metas := []api.PoolMetadata{meta1, meta2}
			expectedPools := []api.Pool{}
			// Go through meta list and append them to expectedPools.
			for _, v := range metas {
				p, _ := c.Pools().Get(v)
				expectedPools = append(expectedPools, *p)
			}

			// Assert the returned poolList is has the meta1 and meta2 pools.
			Expect(poolList.Items).To(Equal(expectedPools))

			By("List a specific pool and compare")

			// Get a pool list with meta1.
			poolList, outError = c.Pools().List(meta1)
			log.Println("Get pool list returns: ", poolList.Items)

			// Get a pool with meta1.
			outPool1, outError1 = c.Pools().Get(meta1)

			// Assert they are equal and no errors.
			Expect(poolList.Items[0].Spec).To(Equal(outPool1.Spec))
			Expect(outError1).NotTo(HaveOccurred())

			By("Delete, Get and assert error")

			// Delete a pool with meta1.
			outError1 = c.Pools().Delete(meta1)

			// Get a pool with meta1.
			_, outError = c.Pools().Get(meta1)

			// Expect an error since the pool was deleted.
			Expect(outError.Error()).To(Equal(errors.New("resource does not exist: Pool(cidr=10.0.0.0/24)").Error()))

			// Delete the second pool with meta2.
			outError1 = c.Pools().Delete(meta2)

			By("Delete all the pools, Get pool list and expect empty pool list")

			// Both pools are deleted in the calls above.
			// Get the list of all the pools.
			poolList, outError = c.Pools().List(api.PoolMetadata{})
			log.Println("Get pool list returns: ", poolList.Items)

			// Create an empty pool list.
			// Note: you can't use make([]api.Pool, 0) because it creates an empty underlying struct,
			// whereas new([]api.Pool) just returns a pointer without creating an empty struct.
			emptyPoolList := new([]api.Pool)

			// Expect returned poolList to contain empty poolList.
			Expect(poolList.Items).To(Equal(*emptyPoolList))
		},

		// Test 1: Pass two fully populated PoolSpecs and expect the series of operations to succeed.
		Entry("Two fully populated PoolSpecs",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("192.168.0.0/24")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: true,
				Disabled:    true,
			},
			api.PoolSpec{
				IPIP:        nil,
				NATOutgoing: true,
				Disabled:    false,
			}),

		// Test 2: Pass one partially populated PoolSpec and another fully populated PoolSpec and expect the series of operations to succeed.
		Entry("One partially populated PoolSpec and another fully populated PoolSpec",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("192.168.0.0/24")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
			},
			api.PoolSpec{
				IPIP:        nil,
				NATOutgoing: true,
				Disabled:    true,
			}),

		// Test 3: Pass one fully populated PoolSpec and another empty PoolSpec and expect the series of operations to succeed.
		Entry("One fully populated PoolSpec and another empty PoolSpec",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("192.168.0.0/24")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: true,
				Disabled:    true,
			},
			api.PoolSpec{},
		),

		// Test 4: Pass two fully populated PoolSpecs with two PoolMetadata (one IPv4 and another IPv6) and expect the series of operations to succeed.
		Entry("Two fully populated PoolSpecs with two PoolMetadata (one IPv4 and another IPv6)",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("fe80::00/120")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: true,
				Disabled:    true,
			},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: false,
				Disabled:    false,
			}),
	)
})
<commit_msg>assert errors first<commit_after>// Copyright (c) 2016 Tigera, Inc. All rights reserved.

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

// Test cases (Pool object e2e):
// Test 1: Pass two fully populated PoolSpecs and expect the series of operations to succeed.
// Test 2: Pass one partially populated PoolSpec and another fully populated PoolSpec and expect the series of operations to succeed.
// Test 3: Pass one fully populated PoolSpec and another empty PoolSpec and expect the series of operations to succeed.
// Test 4: Pass two fully populated PoolSpecs with two PoolMetadata (one IPv4 and another IPv6) and expect the series of operations to succeed.

// Series of operations each test goes through:
// Update meta1 - check for failure (because it doesn't exist).
// Create meta1 with spec1.
// Apply meta2 with spec2.
// Get meta1 and meta2, compare spec1 and spec2.
// Update meta1 with spec2.
// Get meta1 compare spec2.
// List (empty Meta) ... Get meta1 and meta2.
// List (using Meta1) ... Get meta1.
// Delete meta1.
// Get meta1 ... fail.
// Delete meta2.
// List (empty Meta) ... Get no entries (should not error).

package client_test

import (
	"errors"
	"log"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"github.com/projectcalico/libcalico-go/lib/api"
	"github.com/projectcalico/libcalico-go/lib/testutils"
)

var _ = Describe("Pool tests", func() {

	DescribeTable("Pool e2e tests",
		func(meta1, meta2 api.PoolMetadata, spec1, spec2 api.PoolSpec) {

			// Erase etcd clean.
			testutils.CleanEtcd()

			// Create a new client.
			c, err := testutils.NewClient("")
			if err != nil {
				log.Println("Error creating client:", err)
			}
			By("Updating the pool before it is created")
			_, outError := c.Pools().Update(&api.Pool{Metadata: meta1, Spec: spec1})

			// Should return an error.
			Expect(outError.Error()).To(Equal(errors.New("resource does not exist: Pool(cidr=10.0.0.0/24)").Error()))

			By("Create, Apply, Get and compare")

			// Create a pool with meta1 and spec1.
			_, outError = c.Pools().Create(&api.Pool{Metadata: meta1, Spec: spec1})

			// Apply a pool with meta2 and spec2.
			_, outError = c.Pools().Apply(&api.Pool{Metadata: meta2, Spec: spec2})

			// Get pool with meta1.
			outPool1, outError1 := c.Pools().Get(meta1)
			log.Println("Out Pool object: ", outPool1)

			// Get pool with meta2.
			outPool2, outError2 := c.Pools().Get(meta2)
			log.Println("Out Pool object: ", outPool2)

			// Should match spec1 & outPool1 and outPool2 & spec2 and errors to be nil.
			Expect(outError1).NotTo(HaveOccurred())
			Expect(outError2).NotTo(HaveOccurred())
			Expect(outPool1.Spec).To(Equal(spec1))
			Expect(outPool2.Spec).To(Equal(spec2))

			By("Update, Get and compare")

			// Update meta1 pool with spec2.
			c.Pools().Update(&api.Pool{Metadata: meta1, Spec: spec2})

			// Get pool with meta1.
			outPool1, outError1 = c.Pools().Get(meta1)

			// Assert the Spec for pool with meta1 matches spec2 and no error.
			Expect(outError1).NotTo(HaveOccurred())
			Expect(outPool1.Spec).To(Equal(spec2))

			By("List all the pools and compare")

			// Get a list of pools.
			poolList, outError := c.Pools().List(api.PoolMetadata{})
			log.Println("Get pool list returns: ", poolList.Items)
			metas := []api.PoolMetadata{meta1, meta2}
			expectedPools := []api.Pool{}
			// Go through meta list and append them to expectedPools.
			for _, v := range metas {
				p, _ := c.Pools().Get(v)
				expectedPools = append(expectedPools, *p)
			}

			// Assert the returned poolList is has the meta1 and meta2 pools.
			Expect(poolList.Items).To(Equal(expectedPools))

			By("List a specific pool and compare")

			// Get a pool list with meta1.
			poolList, outError = c.Pools().List(meta1)
			log.Println("Get pool list returns: ", poolList.Items)

			// Get a pool with meta1.
			outPool1, outError1 = c.Pools().Get(meta1)

			// Assert they are equal and no errors.
			Expect(outError1).NotTo(HaveOccurred())
			Expect(poolList.Items[0].Spec).To(Equal(outPool1.Spec))

			By("Delete, Get and assert error")

			// Delete a pool with meta1.
			outError1 = c.Pools().Delete(meta1)

			// Get a pool with meta1.
			_, outError = c.Pools().Get(meta1)

			// Expect an error since the pool was deleted.
			Expect(outError.Error()).To(Equal(errors.New("resource does not exist: Pool(cidr=10.0.0.0/24)").Error()))

			// Delete the second pool with meta2.
			outError1 = c.Pools().Delete(meta2)

			By("Delete all the pools, Get pool list and expect empty pool list")

			// Both pools are deleted in the calls above.
			// Get the list of all the pools.
			poolList, outError = c.Pools().List(api.PoolMetadata{})
			log.Println("Get pool list returns: ", poolList.Items)

			// Create an empty pool list.
			// Note: you can't use make([]api.Pool, 0) because it creates an empty underlying struct,
			// whereas new([]api.Pool) just returns a pointer without creating an empty struct.
			emptyPoolList := new([]api.Pool)

			// Expect returned poolList to contain empty poolList.
			Expect(poolList.Items).To(Equal(*emptyPoolList))
		},

		// Test 1: Pass two fully populated PoolSpecs and expect the series of operations to succeed.
		Entry("Two fully populated PoolSpecs",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("192.168.0.0/24")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: true,
				Disabled:    true,
			},
			api.PoolSpec{
				IPIP:        nil,
				NATOutgoing: true,
				Disabled:    false,
			}),

		// Test 2: Pass one partially populated PoolSpec and another fully populated PoolSpec and expect the series of operations to succeed.
		Entry("One partially populated PoolSpec and another fully populated PoolSpec",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("192.168.0.0/24")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
			},
			api.PoolSpec{
				IPIP:        nil,
				NATOutgoing: true,
				Disabled:    true,
			}),

		// Test 3: Pass one fully populated PoolSpec and another empty PoolSpec and expect the series of operations to succeed.
		Entry("One fully populated PoolSpec and another empty PoolSpec",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("192.168.0.0/24")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: true,
				Disabled:    true,
			},
			api.PoolSpec{},
		),

		// Test 4: Pass two fully populated PoolSpecs with two PoolMetadata (one IPv4 and another IPv6) and expect the series of operations to succeed.
		Entry("Two fully populated PoolSpecs with two PoolMetadata (one IPv4 and another IPv6)",
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("10.0.0.0/24")},
			api.PoolMetadata{CIDR: testutils.MustParseCIDR("fe80::00/120")},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: true,
				Disabled:    true,
			},
			api.PoolSpec{
				IPIP: &api.IPIPConfiguration{
					Enabled: true,
				},
				NATOutgoing: false,
				Disabled:    false,
			}),
	)
})
<|endoftext|>