<commit_before>package simpledb

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

	"github.com/garyburd/redigo/redis"
)

type DB struct {
	Name string
}

func Open(name string) *DB {
	return &DB{Name: name}
}

func (db *DB) Close() {
}

func (db *DB) GenMaxIdKey() (maxIdKey string) {
	return fmt.Sprintf("%v/maxid", db.Name)
}

func (db *DB) GetMaxId(c redis.Conn) (maxId uint64, err error) {
	k := db.GenMaxIdKey()
	exists, err := redis.Bool(c.Do("EXISTS", k))
	if err != nil {
		debugPrintf("GenMaxId() error: %v\n", err)
		return 0, err
	}

	if !exists {
		return 0, nil
	}

	maxId, err = redis.Uint64(c.Do("GET", k))
	if err != nil {
		debugPrintf("GenMaxId() error: %v\n", err)
		return 0, err
	}

	return maxId, nil
}

func (db *DB) GenMaxBucketIdKey() (maxBucketIdKey string) {
	return fmt.Sprintf("%v/maxbucketid", db.Name)
}

func (db *DB) GetMaxBucketId(c redis.Conn) (maxBucketId uint64, err error) {
	k := db.GenMaxBucketIdKey()
	exists, err := redis.Bool(c.Do("EXISTS", k))
	if err != nil {
		debugPrintf("GetMaxBucketId() error: %v\n", err)
		return 0, err
	}

	if !exists {
		_, err := redis.String(c.Do("SET", k, 1))
		if err != nil {
			debugPrintf("GetMaxBucketId() error: %v\n", err)
			return 0, err
		}

		return 1, nil
	}

	maxBucketId, err = redis.Uint64(c.Do("GET", k))
	if err != nil {
		debugPrintf("GetMaxBucketId() error: %v\n", err)
		return 0, err
	}

	return maxBucketId, nil
}

func (db *DB) GenHashKey(bucketId uint64) (recordHashKey, indexHashKey string) {
	bucketIdStr := strconv.FormatUint(bucketId, 10)
	recordHashKey = fmt.Sprintf("%v/bucket/%v", db.Name, bucketIdStr)
	indexHashKey = fmt.Sprintf("%v/idx/bucket/%v", db.Name, bucketIdStr)
	return recordHashKey, indexHashKey
}

func (db *DB) IndexExists(c redis.Conn, data string) (exists bool, err error) {
	maxBucketId, err := db.GetMaxBucketId(c)
	if err != nil {
		debugPrintf("GetMaxBucketId() error: %v\n", err)
		return false, err
	}

	indexHashKey := ""
	indexHashField := data

	for i := maxBucketId; i >= 1; i-- {
		_, indexHashKey = db.GenHashKey(i)
		exists, err := redis.Bool(c.Do("HEXISTS", indexHashKey, indexHashField))
		if err != nil {
			debugPrintf("IndexExists() error: %v\n", err)
			return false, err
		}

		if exists {
			return true, nil
		}
	}
	return false, nil
}

func (db *DB) Create(c redis.Conn, data string) (id string, err error) {
	// 1. Check json data.
	if len(data) == 0 {
		err = errors.New("Empty data.")
		debugPrintf("Create() error: %v\n", err)
		return "", err
	}

	// 2. Get max id and compute current id.
	maxId, err := db.GetMaxId(c)
	if err != nil {
		debugPrintf("Create() error: %v\n", err)
		return "", err
	}

	nId := maxId + 1

	// 3. Compute current bucket id.
	//    Increase max bucket id if current bucket id > max bucket id.
	bucketId := ComputeBucketId(nId)

	maxBucketId, err := db.GetMaxBucketId(c)
	if err != nil {
		debugPrintf("Create() error: %v\n", err)
		return "", err
	}

	if bucketId > maxBucketId {
		k := db.GenMaxBucketIdKey()
		_, err := c.Do("INCR", k)
		if err != nil {
			debugPrintf("Create() error: %v\n", err)
			return "", err
		}
	}

	// 4. Generate hash key for record and index.
	recordHashKey, indexHashKey := db.GenHashKey(bucketId)

	// 5. Check if data already exists.
	exists, err := db.IndexExists(c, data)
	if err != nil {
		debugPrintf("Create(): error: %v\n", err)
		return "", err
	}

	// Index already exists, it means the record also exists.
	if exists {
		err = errors.New("Data already exists.")
		debugPrintf("Create(): error: %v\n", err)
		return "", err
	}

	// 6. Create record and index
	recordHashField := nId
	indexHashField := data
	maxIdKey := db.GenMaxIdKey()

	c.Send("MULTI")
	c.Send("HSET", recordHashKey, recordHashField, data)
	c.Send("HSET", indexHashKey, indexHashField, nId)
	c.Send("INCR", maxIdKey)
	ret, err := c.Do("EXEC")
	if err != nil {
		debugPrintf("Create() error: %v\n", err)
		return "", err
	}

	debugPrintf("Create(): ok: %v\n", ret)

	id = strconv.FormatUint(nId, 10)
	return id, nil
}

func (db *DB) BatchCreate(c redis.Conn, dataArr []string) (ids []string, err error) {
	var checkedData map[string]int = make(map[string]int) // key: data, value: order in dataArr.
	var nId, maxId, bucketId, maxBucketId, recordHashField uint64
	var recordHashKey, indexHashKey, indexHashField, maxIdKey string
	var ret interface{}
	err = nil
	ids = []string{}
	id := ""
	ok := false
	exists := false
	k := ""
	alreadySendMULTI := false

	// Get max id.
	maxId, err = db.GetMaxId(c)
	if err != nil {
		goto end
	}

	nId = maxId + 1

	c.Send("MULTI")
	alreadySendMULTI = true

	// Check data and send command to pipeline.
	for i, data := range dataArr {
		// Check empty data.
		if len(data) == 0 {
			err = errors.New("Empty data.")
			goto end
		}

		// Check redundant data in dataArr.
		if _, ok = checkedData[data]; ok {
			err = errors.New(fmt.Sprintf("Redundant data found in dataArr: %v", data))
			goto end
		}
		checkedData[data] = i

		// Check if data already exist in db.
		exists, err = db.IndexExists(c, data)
		if err != nil {
			goto end
		}

		if exists {
			err = errors.New(fmt.Sprintf("Data already exists in db: %v.", data))
			goto end
		}

		// Increase Id
		nId = maxId + uint64(i+1)
		id = strconv.FormatUint(nId, 10)
		// Insert to result id array
		ids = append(ids, id)

		// Compute bucket id.
		bucketId = ComputeBucketId(nId)

		maxBucketId, err = db.GetMaxBucketId(c)
		if err != nil {
			goto end
		}

		// Increase max bucket id if need.
		if bucketId > maxBucketId {
			k = db.GenMaxBucketIdKey()
			_, err = c.Do("INCR", k)
			if err != nil {
				goto end
			}
		}

		// Generate hash key for record and index.
		recordHashKey, indexHashKey = db.GenHashKey(bucketId)

		// Create record and index.
		recordHashField = nId
		indexHashField = data
		maxIdKey = db.GenMaxIdKey()

		c.Send("HSET", recordHashKey, recordHashField, data)
		c.Send("HSET", indexHashKey, indexHashField, nId)
		c.Send("INCR", maxIdKey)
	}

	// Do piplined transaction.
	ret, err = c.Do("EXEC")
	if err != nil {
		goto end
	}

	debugPrintf("BatchCreate() ok. ret: %v, ids: %v\n", ret, ids)

end:
	if err != nil {
		if alreadySendMULTI {
			c.Do("DISCARD")
		}
		debugPrintf("BatchCreate() error: %v\n", err)
		return []string{}, err
	}

	return ids, nil
}

func (db *DB) Exists(c redis.Conn, id string) (exists bool, recordHashKey, indexHashKey string, recordHashField uint64, err error) {
	nId, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		debugPrintf("Exists() strconv.ParseUint() error: %v\n", err)
		return false, "", "", 0, err
	}

	bucketId := ComputeBucketId(nId)
	recordHashKey, indexHashKey = db.GenHashKey(bucketId)
	recordHashField = nId

	exists, err = redis.Bool(c.Do("HEXISTS", recordHashKey, recordHashField))
	if err != nil {
		debugPrintf("Exists() error: %v\n", err)
		return false, "", "", 0, err
	}

	return exists, recordHashKey, indexHashKey, recordHashField, nil
}

func (db *DB) Get(c redis.Conn, id string) (data string, err error) {
	exists, recordHashKey, _, recordHashField, err := db.Exists(c, id)
	if err != nil {
		debugPrintf("Get(): db.Exists() error: %v\n", err)
		return "", err
	}

	if !exists {
		err = errors.New("Record filed does not exists in hash key.")
		debugPrintf("Get(): error: %v\n", err)
		return "", err
	}

	data, err = redis.String(c.Do("HGET", recordHashKey, recordHashField))
	if err != nil {
		debugPrintf("Get(): error: %v\n", err)
		return "", err
	}

	return data, nil
}

func (db *DB) BatchGet(c redis.Conn, ids []string) (dataMap map[string]string, err error) {
	dataMap = make(map[string]string)
	data := ""

	for _, id := range ids {
		data, err = db.Get(c, id)
		if err != nil {
			debugPrintf("BatchGet(): db.Get() error: %v\n", err)
			return dataMap, err
		}
		dataMap[id] = data
	}
	return dataMap, nil
}

func (db *DB) Update(c redis.Conn, id, data string) error {
	exists, recordHashKey, indexHashKey, recordHashField, err := db.Exists(c, id)
	if err != nil {
		debugPrintf("Update() db.Exists() error: %v\n", err)
		return err
	}

	if !exists {
		err = errors.New("Record does not exist.")
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	oldData, err := db.Get(c, id)
	if err != nil {
		debugPrintf("Update() db.Get() error: %v\n", err)
		return err
	}

	oldIndexHashField := oldData
	newIndexHashField := data
	nId := recordHashField

	// Check if data already exists.
	exists, err = db.IndexExists(c, data)
	if err != nil {
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	// Index already exists, it means the there's already a record has the same value / index.
	if exists {
		err = errors.New("Same data / index already exists.")
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	c.Send("MULTI")
	c.Send("HSET", recordHashKey, recordHashField, data)
	c.Send("HSET", indexHashKey, newIndexHashField, nId)
	c.Send("HDEL", indexHashKey, oldIndexHashField)
	ret, err := c.Do("EXEC")
	if err != nil {
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	debugPrintf("Update() ok. ret: %v\n", ret)

	return nil
}

func (db *DB) Search(c redis.Conn, pattern string) (ids []string, err error) {
	if len(pattern) == 0 {
		err = errors.New("Empty pattern.")
		debugPrintf("Search() error: %v\n", err)
		return []string{}, err
	}

	maxBucketId, err := db.GetMaxBucketId(c)
	if err != nil {
		debugPrintf("Search() error: %v\n", err)
		return []string{}, err
	}

	indexHashKey := ""
	ids = []string{}
	items := []string{}

	for i := maxBucketId; i >= 1; i-- {
		_, indexHashKey = db.GenHashKey(i)
		cursor := 0
		for {
			v, err := redis.Values(c.Do("HSCAN", indexHashKey, cursor, "match", pattern))
			if err != nil {
				debugPrintf("Search(): HSCAN error: %v\n", err)
				return []string{}, err
			}

			v, err = redis.Scan(v, &cursor, &items)
			if err != nil {
				fmt.Printf("err: %v\n", err)
				return []string{}, err
			}

			l := len(items)
			if l > 0 && l%2 == 0 {
				for m := 1; m < l; m += 2 {
					ids = append(ids, items[m])
				}
			}

			if cursor == 0 {
				break
			}
		}
	}
	return ids, nil
}
<commit_msg>Use pipelined transaction to implement DB.BatchCreate() and DB.Create().<commit_after>package simpledb

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

	"github.com/garyburd/redigo/redis"
)

type DB struct {
	Name string
}

func Open(name string) *DB {
	return &DB{Name: name}
}

func (db *DB) Close() {
}

func (db *DB) GenMaxIdKey() (maxIdKey string) {
	return fmt.Sprintf("%v/maxid", db.Name)
}

func (db *DB) GetMaxId(c redis.Conn) (maxId uint64, err error) {
	k := db.GenMaxIdKey()
	exists, err := redis.Bool(c.Do("EXISTS", k))
	if err != nil {
		goto end
	}

	if !exists {
		maxId = 0
		goto end
	}

	maxId, err = redis.Uint64(c.Do("GET", k))
	if err != nil {
		goto end
	}

end:
	if err != nil {
		debugPrintf("GenMaxId() error: %v\n", err)
		return 0, err
	}

	return maxId, nil
}

func (db *DB) GenMaxBucketIdKey() (maxBucketIdKey string) {
	return fmt.Sprintf("%v/maxbucketid", db.Name)
}

func (db *DB) GetMaxBucketId(c redis.Conn) (maxBucketId uint64, err error) {
	k := db.GenMaxBucketIdKey()
	exists, err := redis.Bool(c.Do("EXISTS", k))
	if err != nil {
		debugPrintf("XX error: %v\n", err)
		goto end
	}

	if !exists {
		_, err := c.Do("SET", k, 1)
		if err != nil {
			goto end
		}
	}

	maxBucketId, err = redis.Uint64(c.Do("GET", k))
	if err != nil {
		goto end
	}

end:
	if err != nil {
		debugPrintf("GetMaxBucketId() error: %v\n", err)
		return 1, err
	}
	return maxBucketId, nil
}

func (db *DB) GenHashKey(bucketId uint64) (recordHashKey, indexHashKey string) {
	bucketIdStr := strconv.FormatUint(bucketId, 10)
	recordHashKey = fmt.Sprintf("%v/bucket/%v", db.Name, bucketIdStr)
	indexHashKey = fmt.Sprintf("%v/idx/bucket/%v", db.Name, bucketIdStr)
	return recordHashKey, indexHashKey
}

func (db *DB) IndexExists(c redis.Conn, data string) (exists bool, err error) {
	maxBucketId, err := db.GetMaxBucketId(c)
	if err != nil {
		debugPrintf("GetMaxBucketId() error: %v\n", err)
		return false, err
	}

	indexHashKey := ""
	indexHashField := data

	for i := maxBucketId; i >= 1; i-- {
		_, indexHashKey = db.GenHashKey(i)
		exists, err := redis.Bool(c.Do("HEXISTS", indexHashKey, indexHashField))
		if err != nil {
			debugPrintf("IndexExists() error: %v\n", err)
			return false, err
		}

		if exists {
			return true, nil
		}
	}
	return false, nil
}

func (db *DB) Create(c redis.Conn, data string) (id string, err error) {
	ids := []string{}
	ids, err = db.BatchCreate(c, []string{data})
	if err != nil {
		goto end
	}

	if len(ids) != 1 {
		err = errors.New("Count of created record != 1.")
		goto end
	}

end:
	if err != nil {
		debugPrintf("Create() error: %v\n", err)
		return "", err
	}

	return ids[0], nil
}

func (db *DB) BatchCreate(c redis.Conn, dataArr []string) (ids []string, err error) {
	var checkedData map[string]int = make(map[string]int) // key: data, value: order in dataArr.
	var nId, maxId, bucketId, maxBucketId, recordHashField uint64
	var maxBucketIdKey, recordHashKey, indexHashKey, indexHashField, maxIdKey string
	var ret interface{}
	err = nil
	ids = []string{}
	id := ""
	ok := false
	exists := false
	alreadySendMULTI := false

	// Check data.
	for i, data := range dataArr {
		// Check empty data.
		if len(data) == 0 {
			err = errors.New("Empty data.")
			goto end
		}

		// Check redundant data in dataArr.
		if _, ok = checkedData[data]; ok {
			err = errors.New(fmt.Sprintf("Redundant data found in dataArr: %v", data))
			goto end
		}
		checkedData[data] = i

		// Check if data already exist in db.
		exists, err = db.IndexExists(c, data)
		if err != nil {
			goto end
		}

		if exists {
			err = errors.New(fmt.Sprintf("Data already exists in db: %v.", data))
			goto end
		}
	}

	// Get max id.
	maxId, err = db.GetMaxId(c)
	if err != nil {
		goto end
	}

	// Get max id key.
	maxIdKey = db.GenMaxIdKey()

	// Get max bucket id.
	maxBucketId, err = db.GetMaxBucketId(c)
	if err != nil {
		goto end
	}

	// Prepare piplined transaction.
	c.Send("MULTI")
	alreadySendMULTI = true

	for i, data := range dataArr {
		// Increase Id
		nId = maxId + uint64(i+1)
		id = strconv.FormatUint(nId, 10)
		// Insert to result id array
		ids = append(ids, id)

		// Compute bucket id.
		bucketId = ComputeBucketId(nId)

		// Increase max bucket id if need.
		if bucketId > maxBucketId {
			c.Send("SET", maxBucketIdKey, bucketId)
		}

		// Generate hash key for record and index.
		recordHashKey, indexHashKey = db.GenHashKey(bucketId)

		// Create record and index.
		recordHashField = nId
		indexHashField = data

		c.Send("HSET", recordHashKey, recordHashField, data)
		c.Send("HSET", indexHashKey, indexHashField, nId)
		c.Send("INCR", maxIdKey)
	}

	// Do piplined transaction.
	ret, err = c.Do("EXEC")
	if err != nil {
		goto end
	}

	debugPrintf("BatchCreate() ok. ret: %v, ids: %v\n", ret, ids)

end:
	if err != nil {
		if alreadySendMULTI {
			c.Do("DISCARD")
		}
		debugPrintf("BatchCreate() error: %v\n", err)
		return []string{}, err
	}

	return ids, nil
}

func (db *DB) Exists(c redis.Conn, id string) (exists bool, recordHashKey, indexHashKey string, recordHashField uint64, err error) {
	nId, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		debugPrintf("Exists() strconv.ParseUint() error: %v\n", err)
		return false, "", "", 0, err
	}

	bucketId := ComputeBucketId(nId)
	recordHashKey, indexHashKey = db.GenHashKey(bucketId)
	recordHashField = nId

	exists, err = redis.Bool(c.Do("HEXISTS", recordHashKey, recordHashField))
	if err != nil {
		debugPrintf("Exists() error: %v\n", err)
		return false, "", "", 0, err
	}

	return exists, recordHashKey, indexHashKey, recordHashField, nil
}

func (db *DB) Get(c redis.Conn, id string) (data string, err error) {
	exists, recordHashKey, _, recordHashField, err := db.Exists(c, id)
	if err != nil {
		debugPrintf("Get(): db.Exists() error: %v\n", err)
		return "", err
	}

	if !exists {
		err = errors.New("Record filed does not exists in hash key.")
		debugPrintf("Get(): error: %v\n", err)
		return "", err
	}

	data, err = redis.String(c.Do("HGET", recordHashKey, recordHashField))
	if err != nil {
		debugPrintf("Get(): error: %v\n", err)
		return "", err
	}

	return data, nil
}

func (db *DB) BatchGet(c redis.Conn, ids []string) (dataMap map[string]string, err error) {
	dataMap = make(map[string]string)
	data := ""

	for _, id := range ids {
		data, err = db.Get(c, id)
		if err != nil {
			debugPrintf("BatchGet(): db.Get() error: %v\n", err)
			return dataMap, err
		}
		dataMap[id] = data
	}
	return dataMap, nil
}

func (db *DB) Update(c redis.Conn, id, data string) error {
	exists, recordHashKey, indexHashKey, recordHashField, err := db.Exists(c, id)
	if err != nil {
		debugPrintf("Update() db.Exists() error: %v\n", err)
		return err
	}

	if !exists {
		err = errors.New("Record does not exist.")
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	oldData, err := db.Get(c, id)
	if err != nil {
		debugPrintf("Update() db.Get() error: %v\n", err)
		return err
	}

	oldIndexHashField := oldData
	newIndexHashField := data
	nId := recordHashField

	// Check if data already exists.
	exists, err = db.IndexExists(c, data)
	if err != nil {
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	// Index already exists, it means the there's already a record has the same value / index.
	if exists {
		err = errors.New("Same data / index already exists.")
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	c.Send("MULTI")
	c.Send("HSET", recordHashKey, recordHashField, data)
	c.Send("HSET", indexHashKey, newIndexHashField, nId)
	c.Send("HDEL", indexHashKey, oldIndexHashField)
	ret, err := c.Do("EXEC")
	if err != nil {
		debugPrintf("Update() error: %v\n", err)
		return err
	}

	debugPrintf("Update() ok. ret: %v\n", ret)

	return nil
}

func (db *DB) Search(c redis.Conn, pattern string) (ids []string, err error) {
	if len(pattern) == 0 {
		err = errors.New("Empty pattern.")
		debugPrintf("Search() error: %v\n", err)
		return []string{}, err
	}

	maxBucketId, err := db.GetMaxBucketId(c)
	if err != nil {
		debugPrintf("Search() error: %v\n", err)
		return []string{}, err
	}

	indexHashKey := ""
	ids = []string{}
	items := []string{}

	for i := maxBucketId; i >= 1; i-- {
		_, indexHashKey = db.GenHashKey(i)
		cursor := 0
		for {
			v, err := redis.Values(c.Do("HSCAN", indexHashKey, cursor, "match", pattern))
			if err != nil {
				debugPrintf("Search(): HSCAN error: %v\n", err)
				return []string{}, err
			}

			v, err = redis.Scan(v, &cursor, &items)
			if err != nil {
				fmt.Printf("err: %v\n", err)
				return []string{}, err
			}

			l := len(items)
			if l > 0 && l%2 == 0 {
				for m := 1; m < l; m += 2 {
					ids = append(ids, items[m])
				}
			}

			if cursor == 0 {
				break
			}
		}
	}
	return ids, nil
}
<|endoftext|>


<commit_before>package status

import (
	"log"
	"strings"

	"github.com/byuoitav/av-api/base"
	"github.com/byuoitav/av-api/dbo"
)

const FLAG = "STATUS"
const U_TYPE = "2"

func GetAudioStatus(building string, room string) ([]base.AudioDevice, error) {

	log.Printf("Getting status of audio devices")

	const TYPE = "0"

	audioDevices, err := dbo.GetDevicesByBuildingAndRoomAndRole(building, room, "AudioOut")
	if err != nil {
		return []base.AudioDevice{}, err
	}

	var outputs []base.AudioDevice
	for _, device := range audioDevices {

		var state base.AudioDevice
		for _, command := range device.Commands {

			if strings.HasPrefix(command.Name, FLAG) && (strings.HasSuffix(command.Name, TYPE) || strings.HasSuffix(command.Name, U_TYPE)) {

				log.Printf("Querying state of device %s", device.Name)
				//get microservice address
				//get microserivce endpoint
				//build url
				//send request
				//parse response
			}

			outputs = append(outputs, state)

		}

	}

	return outputs, nil
}

func GetDisplayStatus(building string, room string) ([]base.Display, error) {

	log.Printf("Getting status of displays")

	const TYPE = "1"

	displays, err := dbo.GetDevicesByBuildingAndRoomAndRole(building, room, "VideoOut")
	if err != nil {
		return []base.Display{}, err
	}

	var outputs []base.Display
	for _, device := range displays {

		var state base.Display
		for _, command := range device.Commands {

			log.Printf("Querying state of display %s", device.Name)
			if strings.HasPrefix(command.Name, FLAG) && (strings.HasSuffix(command.Name, TYPE) || strings.HasSuffix(command.Name, U_TYPE)) {
				//get microservice address
				//get endpoint
				//build url
				//send request
				//parse response
			}
		}

		outputs = append(outputs, state)

	}

	return outputs, nil
}
<commit_msg>adding status structs<commit_after>package status

type PowerStatus struct {
	Power string `json:"power",omitempty`
}

type BlankedStatus struct {
	Blanked bool `json:"blanked",omitempty`
}

type MuteStatus struct {
	Muted bool `json:"muted",omitempty`
}

type VideoInput struct {
	Input string `json:"input",omitempty`
}

type AudioInput struct {
	Input string `json:"input",omitempty`
}

type AudioList struct {
	Inputs []AudioInput `json"inputs",omitempty`
}

type VideoList struct {
	Inputs []VideoInput `json:"inputs",omitemtpy`
}
<|endoftext|>


<commit_before>package store

import (
	"context"
	"time"
)

// Options contains configuration for the Store
type Options struct {
	// Nodes contains the addresses or other connection information of the backing storage.
	// For example, an etcd implementation would contain the nodes of the cluster.
	// A SQL implementation could contain one or more connection strings.
	Nodes []string
	// Database allows multiple isolated stores to be kept in one backend, if supported.
	Database string
	// Table is analagous to a table in database backends or a key prefix in KV backends
	Table string
	// Context should contain all implementation specific options, using context.WithValue.
	Context context.Context
}

// Option sets values in Options
type Option func(o *Options)

// Nodes contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings.
func Nodes(a ...string) Option {
	return func(o *Options) {
		o.Nodes = a
	}
}

// Database allows multiple isolated stores to be kept in one backend, if supported.
func Database(db string) Option {
	return func(o *Options) {
		o.Database = db
	}
}

// Table is analagous to a table in database backends or a key prefix in KV backends
func Table(t string) Option {
	return func(o *Options) {
		o.Table = t
	}
}

// WithContext sets the stores context, for any extra configuration
func WithContext(c context.Context) Option {
	return func(o *Options) {
		o.Context = c
	}
}

// ReadOptions configures an individual Read operation
type ReadOptions struct {
	// Prefix returns all records that are prefixed with key
	Prefix bool
	// Suffix returns all records that have the suffix key
	Suffix bool
	// Limit limits the number of returned records
	Limit uint
	// Offset when combined with Limit supports pagination
	Offset uint
}

// ReadOption sets values in ReadOptions
type ReadOption func(r *ReadOptions)

// ReadPrefix returns all records that are prefixed with key
func ReadPrefix() ReadOption {
	return func(r *ReadOptions) {
		r.Prefix = true
	}
}

// ReadSuffix returns all records that have the suffix key
func ReadSuffix() ReadOption {
	return func(r *ReadOptions) {
		r.Suffix = true
	}
}

// ReadLimit limits the number of responses to l
func ReadLimit(l uint) ReadOption {
	return func(r *ReadOptions) {
		r.Limit = l
	}
}

// ReadOffset starts returning responses from o. Use in conjunction with Limit for pagination
func ReadOffset(o uint) ReadOption {
	return func(r *ReadOptions) {
		r.Offset = o
	}
}

// WriteOptions configures an individual Write operation
// If Expiry and TTL are set TTL takes precedence
type WriteOptions struct {
	// Expiry is the time the record expires
	Expiry time.Time
	// TTL is the time until the record expires
	TTL time.Duration
}

// WriteOption sets values in WriteOptions
type WriteOption func(w *WriteOptions)

// WriteExpiry is the time the record expires
func WriteExpiry(t time.Time) WriteOption {
	return func(w *WriteOptions) {
		w.Expiry = t
	}
}

// WriteTTL is the time the record expires
func WriteTTL(d time.Duration) WriteOption {
	return func(w *WriteOptions) {
		w.TTL = d
	}
}

// DeleteOptions configures an individual Delete operation
type DeleteOptions struct{}

// DeleteOption sets values in DeleteOptions
type DeleteOption func(d *DeleteOptions)

// ListOptions configures an individual List operation
type ListOptions struct {
	// Prefix returns all keys that are prefixed with key
	Prefix string
	// Suffix returns all keys that end with key
	Suffix string
	// Limit limits the number of returned keys
	Limit uint
	// Offset when combined with Limit supports pagination
	Offset uint
}

// ListOption sets values in ListOptions
type ListOption func(l *ListOptions)

// ListPrefix returns all keys that are prefixed with key
func ListPrefix(p string) ListOption {
	return func(l *ListOptions) {
		l.Prefix = p
	}
}

// ListSuffix returns all keys that end with key
func ListSuffix(s string) ListOption {
	return func(l *ListOptions) {
		l.Suffix = s
	}
}

// ListLimit limits the number of returned keys to l
func ListLimit(l uint) ListOption {
	return func(lo *ListOptions) {
		lo.Limit = l
	}
}

// ListOffset starts returning responses from o. Use in conjunction with Limit for pagination.
func ListOffset(o uint) ListOption {
	return func(l *ListOptions) {
		l.Offset = o
	}
}
<commit_msg>Add options for Database/Table (#1516)<commit_after>package store

import (
	"context"
	"time"
)

// Options contains configuration for the Store
type Options struct {
	// Nodes contains the addresses or other connection information of the backing storage.
	// For example, an etcd implementation would contain the nodes of the cluster.
	// A SQL implementation could contain one or more connection strings.
	Nodes []string
	// Database allows multiple isolated stores to be kept in one backend, if supported.
	Database string
	// Table is analagous to a table in database backends or a key prefix in KV backends
	Table string
	// Context should contain all implementation specific options, using context.WithValue.
	Context context.Context
}

// Option sets values in Options
type Option func(o *Options)

// Nodes contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings.
func Nodes(a ...string) Option {
	return func(o *Options) {
		o.Nodes = a
	}
}

// Database allows multiple isolated stores to be kept in one backend, if supported.
func Database(db string) Option {
	return func(o *Options) {
		o.Database = db
	}
}

// Table is analagous to a table in database backends or a key prefix in KV backends
func Table(t string) Option {
	return func(o *Options) {
		o.Table = t
	}
}

// WithContext sets the stores context, for any extra configuration
func WithContext(c context.Context) Option {
	return func(o *Options) {
		o.Context = c
	}
}

// ReadOptions configures an individual Read operation
type ReadOptions struct {
	Database, Table string
	// Prefix returns all records that are prefixed with key
	Prefix bool
	// Suffix returns all records that have the suffix key
	Suffix bool
	// Limit limits the number of returned records
	Limit uint
	// Offset when combined with Limit supports pagination
	Offset uint
}

// ReadOption sets values in ReadOptions
type ReadOption func(r *ReadOptions)

// ReadFrom the database and table
func ReadFrom(database, table string) ReadOption {
	return func(r *ReadOptions) {
		r.Database = database
		r.Table = table
	}
}

// ReadPrefix returns all records that are prefixed with key
func ReadPrefix() ReadOption {
	return func(r *ReadOptions) {
		r.Prefix = true
	}
}

// ReadSuffix returns all records that have the suffix key
func ReadSuffix() ReadOption {
	return func(r *ReadOptions) {
		r.Suffix = true
	}
}

// ReadLimit limits the number of responses to l
func ReadLimit(l uint) ReadOption {
	return func(r *ReadOptions) {
		r.Limit = l
	}
}

// ReadOffset starts returning responses from o. Use in conjunction with Limit for pagination
func ReadOffset(o uint) ReadOption {
	return func(r *ReadOptions) {
		r.Offset = o
	}
}

// WriteOptions configures an individual Write operation
// If Expiry and TTL are set TTL takes precedence
type WriteOptions struct {
	Database, Table string
	// Expiry is the time the record expires
	Expiry time.Time
	// TTL is the time until the record expires
	TTL time.Duration
}

// WriteOption sets values in WriteOptions
type WriteOption func(w *WriteOptions)

// WriteTo the database and table
func WriteTo(database, table string) WriteOption {
	return func(w *WriteOptions) {
		w.Database = database
		w.Table = table
	}
}

// WriteExpiry is the time the record expires
func WriteExpiry(t time.Time) WriteOption {
	return func(w *WriteOptions) {
		w.Expiry = t
	}
}

// WriteTTL is the time the record expires
func WriteTTL(d time.Duration) WriteOption {
	return func(w *WriteOptions) {
		w.TTL = d
	}
}

// DeleteOptions configures an individual Delete operation
type DeleteOptions struct {
	Database, Table string
}

// DeleteOption sets values in DeleteOptions
type DeleteOption func(d *DeleteOptions)

// DeleteFrom the database and table
func DeleteFrom(database, table string) DeleteOption {
	return func(d *DeleteOptions) {
		d.Database = database
		d.Table = table
	}
}

// ListOptions configures an individual List operation
type ListOptions struct {
	// List from the following
	Database, Table string
	// Prefix returns all keys that are prefixed with key
	Prefix string
	// Suffix returns all keys that end with key
	Suffix string
	// Limit limits the number of returned keys
	Limit uint
	// Offset when combined with Limit supports pagination
	Offset uint
}

// ListOption sets values in ListOptions
type ListOption func(l *ListOptions)

// ListFrom the database and table
func ListFrom(database, table string) ListOption {
	return func(l *ListOptions) {
		l.Database = database
		l.Table = table
	}
}

// ListPrefix returns all keys that are prefixed with key
func ListPrefix(p string) ListOption {
	return func(l *ListOptions) {
		l.Prefix = p
	}
}

// ListSuffix returns all keys that end with key
func ListSuffix(s string) ListOption {
	return func(l *ListOptions) {
		l.Suffix = s
	}
}

// ListLimit limits the number of returned keys to l
func ListLimit(l uint) ListOption {
	return func(lo *ListOptions) {
		lo.Limit = l
	}
}

// ListOffset starts returning responses from o. Use in conjunction with Limit for pagination.
func ListOffset(o uint) ListOption {
	return func(l *ListOptions) {
		l.Offset = o
	}
}
<|endoftext|>


<commit_before>package daemon // import "github.com/docker/docker/daemon"

import (
	"context"
	"strconv"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/container"
	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
	"github.com/docker/docker/restartmanager"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func (daemon *Daemon) setStateCounter(c *container.Container) {
	switch c.StateString() {
	case "paused":
		stateCtr.set(c.ID, "paused")
	case "running":
		stateCtr.set(c.ID, "running")
	default:
		stateCtr.set(c.ID, "stopped")
	}
}

func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontainerdtypes.EventInfo) error {
	c.Lock()

	ec, et, err := daemon.containerd.DeleteTask(context.Background(), c.ID)
	if err != nil {
		logrus.WithError(err).Warnf("failed to delete container %s from containerd", c.ID)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	c.StreamConfig.Wait(ctx)
	cancel()
	c.Reset(false)

	exitStatus := container.ExitStatus{
		ExitCode: int(ec),
		ExitedAt: et,
	}
	if e != nil {
		exitStatus.ExitCode = int(e.ExitCode)
		exitStatus.ExitedAt = e.ExitedAt
		exitStatus.OOMKilled = e.OOMKilled
		if e.Error != nil {
			c.SetError(e.Error)
		}
	}

	restart, wait, err := c.RestartManager().ShouldRestart(ec, daemon.IsShuttingDown() || c.HasBeenManuallyStopped, time.Since(c.StartedAt))
	if err == nil && restart {
		c.RestartCount++
		c.SetRestarting(&exitStatus)
	} else {
		c.SetStopped(&exitStatus)
		defer daemon.autoRemove(c)
	}
	defer c.Unlock() // needs to be called before autoRemove

	// cancel healthcheck here, they will be automatically
	// restarted if/when the container is started again
	daemon.stopHealthchecks(c)
	attributes := map[string]string{
		"exitCode": strconv.Itoa(int(ec)),
	}
	daemon.LogContainerEventWithAttributes(c, "die", attributes)
	daemon.Cleanup(c)
	daemon.setStateCounter(c)
	cpErr := c.CheckpointTo(daemon.containersReplica)

	if err == nil && restart {
		go func() {
			err := <-wait
			if err == nil {
				// daemon.netController is initialized when daemon is restoring containers.
				// But containerStart will use daemon.netController segment.
				// So to avoid panic at startup process, here must wait util daemon restore done.
				daemon.waitForStartupDone()
				if err = daemon.containerStart(c, "", "", false); err != nil {
					logrus.Debugf("failed to restart container: %+v", err)
				}
			}
			if err != nil {
				c.Lock()
				c.SetStopped(&exitStatus)
				daemon.setStateCounter(c)
				c.CheckpointTo(daemon.containersReplica)
				c.Unlock()
				defer daemon.autoRemove(c)
				if err != restartmanager.ErrRestartCanceled {
					logrus.Errorf("restartmanger wait error: %+v", err)
				}
			}
		}()
	}

	return cpErr
}

// ProcessEvent is called by libcontainerd whenever an event occurs
func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
	c, err := daemon.GetContainer(id)
	if err != nil {
		return errors.Wrapf(err, "could not find container %s", id)
	}

	switch e {
	case libcontainerdtypes.EventOOM:
		// StateOOM is Linux specific and should never be hit on Windows
		if isWindows {
			return errors.New("received StateOOM from libcontainerd on Windows. This should never happen")
		}

		c.Lock()
		defer c.Unlock()
		daemon.updateHealthMonitor(c)
		if err := c.CheckpointTo(daemon.containersReplica); err != nil {
			return err
		}

		daemon.LogContainerEvent(c, "oom")
	case libcontainerdtypes.EventExit:
		if int(ei.Pid) == c.Pid {
			return daemon.handleContainerExit(c, &ei)
		}

		exitCode := 127
		if execConfig := c.ExecCommands.Get(ei.ProcessID); execConfig != nil {
			ec := int(ei.ExitCode)
			execConfig.Lock()
			defer execConfig.Unlock()
			execConfig.ExitCode = &ec
			execConfig.Running = false

			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
			execConfig.StreamConfig.Wait(ctx)
			cancel()

			if err := execConfig.CloseStreams(); err != nil {
				logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
			}

			// remove the exec command from the container's store only and not the
			// daemon's store so that the exec command can be inspected.
			c.ExecCommands.Delete(execConfig.ID, execConfig.Pid)

			exitCode = ec
		}
		attributes := map[string]string{
			"execID":   ei.ProcessID,
			"exitCode": strconv.Itoa(exitCode),
		}
		daemon.LogContainerEventWithAttributes(c, "exec_die", attributes)
	case libcontainerdtypes.EventStart:
		c.Lock()
		defer c.Unlock()

		// This is here to handle start not generated by docker
		if !c.Running {
			c.SetRunning(int(ei.Pid), false)
			c.HasBeenManuallyStopped = false
			c.HasBeenStartedBefore = true
			daemon.setStateCounter(c)

			daemon.initHealthMonitor(c)

			if err := c.CheckpointTo(daemon.containersReplica); err != nil {
				return err
			}
			daemon.LogContainerEvent(c, "start")
		}

	case libcontainerdtypes.EventPaused:
		c.Lock()
		defer c.Unlock()

		if !c.Paused {
			c.Paused = true
			daemon.setStateCounter(c)
			daemon.updateHealthMonitor(c)
			if err := c.CheckpointTo(daemon.containersReplica); err != nil {
				return err
			}
			daemon.LogContainerEvent(c, "pause")
		}
	case libcontainerdtypes.EventResumed:
		c.Lock()
		defer c.Unlock()

		if c.Paused {
			c.Paused = false
			daemon.setStateCounter(c)
			daemon.updateHealthMonitor(c)

			if err := c.CheckpointTo(daemon.containersReplica); err != nil {
				return err
			}
			daemon.LogContainerEvent(c, "unpause")
		}
	}
	return nil
}

func (daemon *Daemon) autoRemove(c *container.Container) {
	c.Lock()
	ar := c.HostConfig.AutoRemove
	c.Unlock()
	if !ar {
		return
	}

	err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{ForceRemove: true, RemoveVolume: true})
	if err == nil {
		return
	}
	if c := daemon.containers.Get(c.ID); c == nil {
		return
	}

	logrus.WithError(err).WithField("container", c.ID).Error("error removing container")
}
<commit_msg>handleContainerExit: put a timeout on containerd DeleteTask<commit_after>package daemon // import "github.com/docker/docker/daemon"

import (
	"context"
	"strconv"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/container"
	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
	"github.com/docker/docker/restartmanager"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func (daemon *Daemon) setStateCounter(c *container.Container) {
	switch c.StateString() {
	case "paused":
		stateCtr.set(c.ID, "paused")
	case "running":
		stateCtr.set(c.ID, "running")
	default:
		stateCtr.set(c.ID, "stopped")
	}
}

func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontainerdtypes.EventInfo) error {
	c.Lock()
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	ec, et, err := daemon.containerd.DeleteTask(ctx, c.ID)
	cancel()
	if err != nil {
		logrus.WithError(err).WithField("container", c.ID).Warnf("failed to delete container from containerd")
	}

	ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
	c.StreamConfig.Wait(ctx)
	cancel()

	c.Reset(false)

	exitStatus := container.ExitStatus{
		ExitCode: int(ec),
		ExitedAt: et,
	}
	if e != nil {
		exitStatus.ExitCode = int(e.ExitCode)
		exitStatus.ExitedAt = e.ExitedAt
		exitStatus.OOMKilled = e.OOMKilled
		if e.Error != nil {
			c.SetError(e.Error)
		}
	}

	restart, wait, err := c.RestartManager().ShouldRestart(ec, daemon.IsShuttingDown() || c.HasBeenManuallyStopped, time.Since(c.StartedAt))
	if err == nil && restart {
		c.RestartCount++
		c.SetRestarting(&exitStatus)
	} else {
		c.SetStopped(&exitStatus)
		defer daemon.autoRemove(c)
	}
	defer c.Unlock() // needs to be called before autoRemove

	// cancel healthcheck here, they will be automatically
	// restarted if/when the container is started again
	daemon.stopHealthchecks(c)
	attributes := map[string]string{
		"exitCode": strconv.Itoa(int(ec)),
	}
	daemon.LogContainerEventWithAttributes(c, "die", attributes)
	daemon.Cleanup(c)
	daemon.setStateCounter(c)
	cpErr := c.CheckpointTo(daemon.containersReplica)

	if err == nil && restart {
		go func() {
			err := <-wait
			if err == nil {
				// daemon.netController is initialized when daemon is restoring containers.
				// But containerStart will use daemon.netController segment.
				// So to avoid panic at startup process, here must wait util daemon restore done.
				daemon.waitForStartupDone()
				if err = daemon.containerStart(c, "", "", false); err != nil {
					logrus.Debugf("failed to restart container: %+v", err)
				}
			}
			if err != nil {
				c.Lock()
				c.SetStopped(&exitStatus)
				daemon.setStateCounter(c)
				c.CheckpointTo(daemon.containersReplica)
				c.Unlock()
				defer daemon.autoRemove(c)
				if err != restartmanager.ErrRestartCanceled {
					logrus.Errorf("restartmanger wait error: %+v", err)
				}
			}
		}()
	}

	return cpErr
}

// ProcessEvent is called by libcontainerd whenever an event occurs
func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
	c, err := daemon.GetContainer(id)
	if err != nil {
		return errors.Wrapf(err, "could not find container %s", id)
	}

	switch e {
	case libcontainerdtypes.EventOOM:
		// StateOOM is Linux specific and should never be hit on Windows
		if isWindows {
			return errors.New("received StateOOM from libcontainerd on Windows. This should never happen")
		}

		c.Lock()
		defer c.Unlock()
		daemon.updateHealthMonitor(c)
		if err := c.CheckpointTo(daemon.containersReplica); err != nil {
			return err
		}

		daemon.LogContainerEvent(c, "oom")
	case libcontainerdtypes.EventExit:
		if int(ei.Pid) == c.Pid {
			return daemon.handleContainerExit(c, &ei)
		}

		exitCode := 127
		if execConfig := c.ExecCommands.Get(ei.ProcessID); execConfig != nil {
			ec := int(ei.ExitCode)
			execConfig.Lock()
			defer execConfig.Unlock()
			execConfig.ExitCode = &ec
			execConfig.Running = false

			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
			execConfig.StreamConfig.Wait(ctx)
			cancel()

			if err := execConfig.CloseStreams(); err != nil {
				logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
			}

			// remove the exec command from the container's store only and not the
			// daemon's store so that the exec command can be inspected.
			c.ExecCommands.Delete(execConfig.ID, execConfig.Pid)

			exitCode = ec
		}
		attributes := map[string]string{
			"execID":   ei.ProcessID,
			"exitCode": strconv.Itoa(exitCode),
		}
		daemon.LogContainerEventWithAttributes(c, "exec_die", attributes)
	case libcontainerdtypes.EventStart:
		c.Lock()
		defer c.Unlock()

		// This is here to handle start not generated by docker
		if !c.Running {
			c.SetRunning(int(ei.Pid), false)
			c.HasBeenManuallyStopped = false
			c.HasBeenStartedBefore = true
			daemon.setStateCounter(c)

			daemon.initHealthMonitor(c)

			if err := c.CheckpointTo(daemon.containersReplica); err != nil {
				return err
			}
			daemon.LogContainerEvent(c, "start")
		}

	case libcontainerdtypes.EventPaused:
		c.Lock()
		defer c.Unlock()

		if !c.Paused {
			c.Paused = true
			daemon.setStateCounter(c)
			daemon.updateHealthMonitor(c)
			if err := c.CheckpointTo(daemon.containersReplica); err != nil {
				return err
			}
			daemon.LogContainerEvent(c, "pause")
		}
	case libcontainerdtypes.EventResumed:
		c.Lock()
		defer c.Unlock()

		if c.Paused {
			c.Paused = false
			daemon.setStateCounter(c)
			daemon.updateHealthMonitor(c)

			if err := c.CheckpointTo(daemon.containersReplica); err != nil {
				return err
			}
			daemon.LogContainerEvent(c, "unpause")
		}
	}
	return nil
}

func (daemon *Daemon) autoRemove(c *container.Container) {
	c.Lock()
	ar := c.HostConfig.AutoRemove
	c.Unlock()
	if !ar {
		return
	}

	err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{ForceRemove: true, RemoveVolume: true})
	if err == nil {
		return
	}
	if c := daemon.containers.Get(c.ID); c == nil {
		return
	}

	logrus.WithError(err).WithField("container", c.ID).Error("error removing container")
}
<|endoftext|>


<commit_before>package gocql

import (
	"github.com/golang/snappy"
)

type Compressor interface {
	Name() string
	Encode(data []byte) ([]byte, error)
	Decode(data []byte) ([]byte, error)
}

// SnappyCompressor implements the Compressor interface and can be used to
// compress incoming and outgoing frames. The snappy compression algorithm
// aims for very high speeds and reasonable compression.
type SnappyCompressor struct{}

func (s SnappyCompressor) Name() string {
	return "snappy"
}

func (s SnappyCompressor) Encode(data []byte) ([]byte, error) {
	return snappy.Encode(nil, data)
}

func (s SnappyCompressor) Decode(data []byte) ([]byte, error) {
	return snappy.Decode(nil, data)
}
<commit_msg>Compressor.go: snappy.Encode no longer returne error.<commit_after>package gocql

import (
	"github.com/golang/snappy"
)

type Compressor interface {
	Name() string
	Encode(data []byte) ([]byte, error)
	Decode(data []byte) ([]byte, error)
}

// SnappyCompressor implements the Compressor interface and can be used to
// compress incoming and outgoing frames. The snappy compression algorithm
// aims for very high speeds and reasonable compression.
type SnappyCompressor struct{}

func (s SnappyCompressor) Name() string {
	return "snappy"
}

func (s SnappyCompressor) Encode(data []byte) ([]byte, error) {
	result := snappy.Encode(nil, data)
	return result, nil
}

func (s SnappyCompressor) Decode(data []byte) ([]byte, error) {
	return snappy.Decode(nil, data)
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"os"
	"path/filepath"
)

var (
	// ErrNoJigRoot is returned when no Jig root can be found
	ErrNoJigRoot = errors.New("Could not find Jig root")
)

const (
	JigDirName = ".jig"
)

func JigRootDir(path string) string {
	return filepath.Join(path, JigDirName)
}

func dirExists(path string) bool {
	if _, err := os.Stat(path); err != nil {
		return false
	}
	return true
}

func IsJigRoot(path string) bool {
	var err error
	if path, err = filepath.Abs(path); err != nil {
		return false
	}
	return dirExists(filepath.Join(path, JigDirName))
}

func FindClosestJigRoot(path string) (string, error) {
	if jigroot := os.Getenv("JIGROOT"); jigroot != "" {
		if jigroot, err := filepath.Abs(jigroot); err != nil {
			return "", err
		} else {
			return jigroot, nil
		}
	}
	if path == "" {
		return "", ErrNoJigRoot
	}
	parent := path
	for parent != "/" {
		if IsJigRoot(parent) {
			return parent, nil
		}
		parent = filepath.Dir(parent)
	}
	return "", ErrNoJigRoot
}

func FindJigRoot() (string, error) {
	return FindClosestJigRoot("")
}

func CreateJigRoot(path string) error {
	jd := filepath.Join(path, JigDirName)
	if !dirExists(jd) {
		return os.MkdirAll(jd, os.ModeDir|0755)
	}
	return nil
}
<commit_msg>Don't freak out if JIGROOT isn't a jig root<commit_after>package config

import (
	"errors"
	"os"
	"path/filepath"
)

var (
	// ErrNoJigRoot is returned when no Jig root can be found
	ErrNoJigRoot = errors.New("Could not find Jig root")
)

const (
	JigDirName = ".jig"
)

func JigRootDir(path string) string {
	return filepath.Join(path, JigDirName)
}

func dirExists(path string) bool {
	if _, err := os.Stat(path); err != nil {
		return false
	}
	return true
}

func IsJigRoot(path string) bool {
	var err error
	if path, err = filepath.Abs(path); err != nil {
		return false
	}
	return dirExists(filepath.Join(path, JigDirName))
}

func FindClosestJigRoot(path string) (string, error) {
	if jigroot := os.Getenv("JIGROOT"); jigroot != "" {
		if jigroot, err := filepath.Abs(jigroot); err != nil {
			return "", err
		} else if IsJigRoot(jigroot) {
			return jigroot, nil
		}
	}
	if path == "" {
		return "", ErrNoJigRoot
	}
	parent := path
	for parent != "/" {
		if IsJigRoot(parent) {
			return parent, nil
		}
		parent = filepath.Dir(parent)
	}
	return "", ErrNoJigRoot
}

func FindJigRoot() (string, error) {
	return FindClosestJigRoot("")
}

func CreateJigRoot(path string) error {
	jd := filepath.Join(path, JigDirName)
	if !dirExists(jd) {
		return os.MkdirAll(jd, os.ModeDir|0755)
	}
	return nil
}
<|endoftext|>


<commit_before>package httpd

import (
	"bufio"
	"fmt"
	"github.com/Symantec/Dominator/sub/scanner"
	"io"
	"net"
	"net/http"
	"net/rpc"
)

type HtmlWriter interface {
	WriteHtml(writer io.Writer)
}

var onlyHtmler HtmlWriter
var onlyFsh *scanner.FileSystemHistory

type Subd int

func (t *Subd) Poll(generation uint64, reply *scanner.FileSystem) error {
	if onlyFsh.FileSystem() != nil {
		*reply = *onlyFsh.FileSystem()
	}
	return nil
}

func StartServer(portNum uint, fsh *scanner.FileSystemHistory) error {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", portNum))
	if err != nil {
		return err
	}
	onlyHtmler = fsh
	onlyFsh = fsh
	http.HandleFunc("/", onlyHandler)
	subd := new(Subd)
	rpc.Register(subd)
	rpc.HandleHTTP()
	go http.Serve(listener, nil)
	return nil
}

func onlyHandler(w http.ResponseWriter, req *http.Request) {
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	fmt.Fprintln(writer, "<title>subd status page</title>")
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "
subd status page
")
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "
")
	onlyHtmler.WriteHtml(writer)
	fmt.Fprintln(writer, "")
}
<commit_msg>Check generation count in subd.Poll() RPC.<commit_after>package httpd

import (
	"bufio"
	"fmt"
	"github.com/Symantec/Dominator/sub/scanner"
	"io"
	"net"
	"net/http"
	"net/rpc"
)

type HtmlWriter interface {
	WriteHtml(writer io.Writer)
}

var onlyHtmler HtmlWriter
var onlyFsh *scanner.FileSystemHistory

type Subd int

func (t *Subd) Poll(generation uint64, reply *scanner.FileSystem) error {
	fs := onlyFsh.FileSystem()
	if fs != nil && generation != onlyFsh.GenerationCount() {
		*reply = *fs
	}
	return nil
}

func StartServer(portNum uint, fsh *scanner.FileSystemHistory) error {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", portNum))
	if err != nil {
		return err
	}
	onlyHtmler = fsh
	onlyFsh = fsh
	http.HandleFunc("/", onlyHandler)
	subd := new(Subd)
	rpc.Register(subd)
	rpc.HandleHTTP()
	go http.Serve(listener, nil)
	return nil
}

func onlyHandler(w http.ResponseWriter, req *http.Request) {
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	fmt.Fprintln(writer, "<title>subd status page</title>")
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "
subd status page
")
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "
")
	onlyHtmler.WriteHtml(writer)
	fmt.Fprintln(writer, "")
}
<|endoftext|>


<commit_before>package pearl

import (
	"io"
	"net"
	"sync"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/mmcloughlin/pearl/fork/tls"

	"github.com/mmcloughlin/pearl/log"
	"github.com/pkg/errors"
)

const (
	defaultCircuitChannelBuffer = 16
)

// CellSender can send a Cell.
type CellSender interface {
	SendCell(Cell) error
}

// CellReceiver can receive Cells.
type CellReceiver interface {
	ReceiveCell() (Cell, error)
}

// CellReceiver can receive legacy Cells (circ ID length 2).
type LegacyCellReceiver interface {
	CellReceiver
	ReceiveLegacyCell() (Cell, error)
}

// Link is a Cell communication layer.
type Link interface {
	CellSender
	CellReceiver
}

type link struct {
	CellSender
	CellReceiver
}

func NewLink(s CellSender, r CellReceiver) Link {
	return link{
		CellSender:   s,
		CellReceiver: r,
	}
}

type CellChan chan Cell

func (ch CellChan) SendCell(cell Cell) error {
	ch <- cell
	return nil
}

func (ch CellChan) ReceiveCell() (Cell, error) {
	cell, ok := <-ch
	if !ok {
		return nil, io.EOF
	}
	return cell, nil
}

type CircuitLink interface {
	Link
	CircID() CircID
	io.Closer
}

type circLink struct {
	Link
	id CircID
	m  *ChannelManager
}

func NewCircuitLink(id CircID, lk Link, m *ChannelManager) CircuitLink {
	return circLink{
		id:   id,
		Link: lk,
		m:    m,
	}
}

func (c circLink) CircID() CircID { return c.id }
func (c circLink) Close() error   { return c.m.Close(c.id) }

// Connection encapsulates a router connection.
type Connection struct {
	router      *Router
	tlsCtx      *TLSContext
	tlsConn     *tls.Conn
	connID      ConnID
	fingerprint []byte
	outbound    bool

	channels *ChannelManager

	rw io.ReadWriter
	CellReceiver
	CellSender

	logger log.Logger
}

// NewServer constructs a server connection.
func NewServer(r *Router, conn net.Conn, logger log.Logger) (*Connection, error) {
	tlsCtx, err := NewTLSContext(r.IdentityKey())
	if err != nil {
		return nil, err
	}
	tlsConn := tlsCtx.ServerConn(conn)
	c := newConnection(r, tlsCtx, tlsConn, logger.With("role", "server"))
	c.outbound = false
	return c, nil
}

// NewClient constructs a client-side connection.
func NewClient(r *Router, conn net.Conn, logger log.Logger) (*Connection, error) {
	tlsCtx, err := NewTLSContext(r.IdentityKey())
	if err != nil {
		return nil, err
	}
	tlsConn := tlsCtx.ClientConn(conn)
	c := newConnection(r, tlsCtx, tlsConn, logger.With("role", "client"))
	c.outbound = true
	return c, nil
}

func newConnection(r *Router, tlsCtx *TLSContext, tlsConn *tls.Conn, logger log.Logger) *Connection {
	rw := tlsConn // TODO(mbm): use bufio
	connID := NewConnID()
	return &Connection{
		router:      r,
		tlsCtx:      tlsCtx,
		tlsConn:     tlsConn,
		connID:      connID,
		fingerprint: nil,

		channels: NewChannelManager(defaultCircuitChannelBuffer),

		rw:           rw,
		CellReceiver: NewCellReader(rw, logger),
		CellSender:   NewCellWriter(rw, logger),

		logger: log.ForConn(logger, tlsConn).With("conn_id", connID),
	}
}

func (c *Connection) newHandshake() *Handshake {
	return &Handshake{
		Conn:        c.tlsConn,
		Link:        NewHandshakeLink(c.rw, c.logger),
		TLSContext:  c.tlsCtx,
		IdentityKey: &c.router.idKey.PublicKey,
		logger:      c.logger,
	}
}

func (c *Connection) ConnID() ConnID {
	return c.connID
}

// Fingerprint returns the fingerprint of the connected peer.
func (c *Connection) Fingerprint() (Fingerprint, error) {
	if c.fingerprint == nil {
		return Fingerprint{}, errors.New("peer fingerprint not established")
	}
	return NewFingerprintFromBytes(c.fingerprint)
}

func (c *Connection) Serve() error {
	c.logger.Info("serving new connection")

	h := c.newHandshake()
	err := h.Server()
	if err != nil {
		log.Err(c.logger, err, "server handshake failed")
		return nil
	}
	c.fingerprint = h.PeerFingerprint
	c.logger.Info("handshake complete")

	// TODO(mbm): register connection
	if err := c.router.connections.AddConnection(c); err != nil {
		return err
	}

	c.readLoop()
	return nil
}

func (c *Connection) StartClient() error {
	h := c.newHandshake()
	err := h.Client()
	if err != nil {
		return errors.Wrap(err, "client handshake failed")
	}
	c.fingerprint = h.PeerFingerprint
	c.logger.Info("handshake complete")

	// TODO(mbm): register connection
	if err := c.router.connections.AddConnection(c); err != nil {
		return err
	}

	// TODO(mbm): goroutine management
	go c.readLoop()

	return nil
}

func (c *Connection) readLoop() {
	var err error
	var cell Cell

	for {
		cell, err = c.ReceiveCell()
		if errors.Cause(err) == io.EOF {
			c.logger.Debug("EOF")
			err = c.cleanup()
			break
		}
		if err != nil {
			break
		}

		logger := CellLogger(c.logger, cell)
		logger.Trace("received cell")

		switch cell.Command() {
		// Cells to be handled by this Connection
		case Create2:
			err = Create2Handler(c, cell) // XXX error return
			if err != nil {
				log.Err(logger, err, "failed to handle create2")
			}
		// Cells related to a circuit
		case Created2, Relay, RelayEarly, Destroy:
			logger.Trace("directing cell to circuit channel")
			ch, ok := c.channels.Channel(cell.CircID())
			if !ok {
				// BUG(mbm): is logging the correct behavior
				logger.Error("unrecognized circ id")
				continue
			}
			ch <- cell
		// Cells to be ignored
		case Padding, Vpadding:
			logger.Debug("skipping padding cell")
		// Something which shouldn't happen
		default:
			logger.Error("no handler registered")
		}
	}

	if err != nil {
		log.Err(c.logger, err, "receive cell error")
	}
	c.logger.Debug("exit read loop")
}

// cleanup cleans up resources related to the connection.
func (c *Connection) cleanup() error {
	c.logger.Info("cleaning up connection")

	// Close all circuit channels.
	c.channels.CloseAll()

	// BUG(mbm): waitgroup to make sure circuits complete any writes?

	// Unregister the connection.
	return c.router.connections.RemoveConnection(c)
}

// Close the connection.
func (c *Connection) Close() error {
	// BUG(mbm): graceful stop to runloop
	var result error
	if err := c.cleanup(); err != nil {
		result = multierror.Append(result, err)
	}
	if err := c.tlsConn.Close(); err != nil {
		result = multierror.Append(result, err)
	}
	return result
}

// GenerateCircuitLink
func (c *Connection) GenerateCircuitLink() CircuitLink {
	id, ch := c.channels.New(c.outbound)
	return NewCircuitLink(id, NewLink(c, CellChan(ch)), c.channels)
}

// NewCircuitLink
func (c *Connection) NewCircuitLink(id CircID) (CircuitLink, error) {
	ch, err := c.channels.NewWithID(id)
	if err != nil {
		return nil, err
	}
	return NewCircuitLink(id, NewLink(c, CellChan(ch)), c.channels), nil
}

func CellLogger(l log.Logger, cell Cell) log.Logger {
	return l.With("cmd", cell.Command()).With("circid", cell.CircID())
}

// ChannelManager manages a collection of cell channels.
type ChannelManager struct {
	channels   map[CircID]chan Cell
	bufferSize int

	sync.RWMutex
}

func NewChannelManager(n int) *ChannelManager {
	return &ChannelManager{
		channels:   make(map[CircID]chan Cell),
		bufferSize: n,
	}
}

func (m *ChannelManager) New(outbound bool) (CircID, chan Cell) {
	m.Lock()
	defer m.Unlock()

	// Reference: https://github.com/torproject/torspec/blob/4074b891e53e8df951fc596ac6758d74da290c60/tor-spec.txt#L931-L933
	//
	//	   In link protocol version 4 or higher, whichever node initiated the
	//	   connection sets its MSB to 1, and whichever node didn't initiate the
	//	   connection sets its MSB to 0.
	//
	msb := uint32(0)
	if outbound {
		msb = uint32(1)
	}

	// BUG(mbm): potential infinite (or at least long) loop to find a new id
	for {
		id := GenerateCircID(msb)
		// 0 is reserved
		if id == 0 {
			continue
		}
		_, exists := m.channels[id]
		if exists {
			continue
		}
		ch := m.newWithID(id)
		return id, ch
	}
}

func (m *ChannelManager) NewWithID(id CircID) (chan Cell, error) {
	m.Lock()
	defer m.Unlock()
	_, exists := m.channels[id]
	if exists {
		return nil, errors.New("cannot override existing channel id")
	}
	return m.newWithID(id), nil
}

func (m *ChannelManager) newWithID(id CircID) chan Cell {
	ch := make(chan Cell, m.bufferSize)
	m.channels[id] = ch
	return ch
}

func (m *ChannelManager) Channel(id CircID) (chan Cell, bool) {
	m.RLock()
	defer m.RUnlock()
	ch, ok := m.channels[id]
	return ch, ok
}

func (m *ChannelManager) Close(id CircID) error {
	m.Lock()
	defer m.Unlock()

	ch, ok := m.channels[id]
	if !ok {
		return errors.New("unknown circuit")
	}

	close(ch)
	delete(m.channels, id)

	return nil
}

func (m *ChannelManager) CloseAll() {
	m.Lock()
	defer m.Unlock()

	for _, ch := range m.channels {
		close(ch)
	}

	m.channels = make(map[CircID]chan Cell)
}
<commit_msg>use mutierr.Combine<commit_after>package pearl

import (
	"io"
	"net"
	"sync"

	"go.uber.org/multierr"

	"github.com/mmcloughlin/pearl/fork/tls"

	"github.com/mmcloughlin/pearl/log"
	"github.com/pkg/errors"
)

const (
	defaultCircuitChannelBuffer = 16
)

// CellSender can send a Cell.
type CellSender interface {
	SendCell(Cell) error
}

// CellReceiver can receive Cells.
type CellReceiver interface {
	ReceiveCell() (Cell, error)
}

// CellReceiver can receive legacy Cells (circ ID length 2).
type LegacyCellReceiver interface {
	CellReceiver
	ReceiveLegacyCell() (Cell, error)
}

// Link is a Cell communication layer.
type Link interface {
	CellSender
	CellReceiver
}

type link struct {
	CellSender
	CellReceiver
}

func NewLink(s CellSender, r CellReceiver) Link {
	return link{
		CellSender:   s,
		CellReceiver: r,
	}
}

type CellChan chan Cell

func (ch CellChan) SendCell(cell Cell) error {
	ch <- cell
	return nil
}

func (ch CellChan) ReceiveCell() (Cell, error) {
	cell, ok := <-ch
	if !ok {
		return nil, io.EOF
	}
	return cell, nil
}

type CircuitLink interface {
	Link
	CircID() CircID
	io.Closer
}

type circLink struct {
	Link
	id CircID
	m  *ChannelManager
}

func NewCircuitLink(id CircID, lk Link, m *ChannelManager) CircuitLink {
	return circLink{
		id:   id,
		Link: lk,
		m:    m,
	}
}

func (c circLink) CircID() CircID { return c.id }
func (c circLink) Close() error   { return c.m.Close(c.id) }

// Connection encapsulates a router connection.
type Connection struct {
	router      *Router
	tlsCtx      *TLSContext
	tlsConn     *tls.Conn
	connID      ConnID
	fingerprint []byte
	outbound    bool

	channels *ChannelManager

	rw io.ReadWriter
	CellReceiver
	CellSender

	logger log.Logger
}

// NewServer constructs a server connection.
func NewServer(r *Router, conn net.Conn, logger log.Logger) (*Connection, error) {
	tlsCtx, err := NewTLSContext(r.IdentityKey())
	if err != nil {
		return nil, err
	}
	tlsConn := tlsCtx.ServerConn(conn)
	c := newConnection(r, tlsCtx, tlsConn, logger.With("role", "server"))
	c.outbound = false
	return c, nil
}

// NewClient constructs a client-side connection.
func NewClient(r *Router, conn net.Conn, logger log.Logger) (*Connection, error) {
	tlsCtx, err := NewTLSContext(r.IdentityKey())
	if err != nil {
		return nil, err
	}
	tlsConn := tlsCtx.ClientConn(conn)
	c := newConnection(r, tlsCtx, tlsConn, logger.With("role", "client"))
	c.outbound = true
	return c, nil
}

func newConnection(r *Router, tlsCtx *TLSContext, tlsConn *tls.Conn, logger log.Logger) *Connection {
	rw := tlsConn // TODO(mbm): use bufio
	connID := NewConnID()
	return &Connection{
		router:      r,
		tlsCtx:      tlsCtx,
		tlsConn:     tlsConn,
		connID:      connID,
		fingerprint: nil,

		channels: NewChannelManager(defaultCircuitChannelBuffer),

		rw:           rw,
		CellReceiver: NewCellReader(rw, logger),
		CellSender:   NewCellWriter(rw, logger),

		logger: log.ForConn(logger, tlsConn).With("conn_id", connID),
	}
}

func (c *Connection) newHandshake() *Handshake {
	return &Handshake{
		Conn:        c.tlsConn,
		Link:        NewHandshakeLink(c.rw, c.logger),
		TLSContext:  c.tlsCtx,
		IdentityKey: &c.router.idKey.PublicKey,
		logger:      c.logger,
	}
}

func (c *Connection) ConnID() ConnID {
	return c.connID
}

// Fingerprint returns the fingerprint of the connected peer.
func (c *Connection) Fingerprint() (Fingerprint, error) {
	if c.fingerprint == nil {
		return Fingerprint{}, errors.New("peer fingerprint not established")
	}
	return NewFingerprintFromBytes(c.fingerprint)
}

func (c *Connection) Serve() error {
	c.logger.Info("serving new connection")

	h := c.newHandshake()
	err := h.Server()
	if err != nil {
		log.Err(c.logger, err, "server handshake failed")
		return nil
	}
	c.fingerprint = h.PeerFingerprint
	c.logger.Info("handshake complete")

	// TODO(mbm): register connection
	if err := c.router.connections.AddConnection(c); err != nil {
		return err
	}

	c.readLoop()
	return nil
}

func (c *Connection) StartClient() error {
	h := c.newHandshake()
	err := h.Client()
	if err != nil {
		return errors.Wrap(err, "client handshake failed")
	}
	c.fingerprint = h.PeerFingerprint
	c.logger.Info("handshake complete")

	// TODO(mbm): register connection
	if err := c.router.connections.AddConnection(c); err != nil {
		return err
	}

	// TODO(mbm): goroutine management
	go c.readLoop()

	return nil
}

func (c *Connection) readLoop() {
	var err error
	var cell Cell

	for {
		cell, err = c.ReceiveCell()
		if errors.Cause(err) == io.EOF {
			c.logger.Debug("EOF")
			err = c.cleanup()
			break
		}
		if err != nil {
			break
		}

		logger := CellLogger(c.logger, cell)
		logger.Trace("received cell")

		switch cell.Command() {
		// Cells to be handled by this Connection
		case Create2:
			err = Create2Handler(c, cell) // XXX error return
			if err != nil {
				log.Err(logger, err, "failed to handle create2")
			}
		// Cells related to a circuit
		case Created2, Relay, RelayEarly, Destroy:
			logger.Trace("directing cell to circuit channel")
			ch, ok := c.channels.Channel(cell.CircID())
			if !ok {
				// BUG(mbm): is logging the correct behavior
				logger.Error("unrecognized circ id")
				continue
			}
			ch <- cell
		// Cells to be ignored
		case Padding, Vpadding:
			logger.Debug("skipping padding cell")
		// Something which shouldn't happen
		default:
			logger.Error("no handler registered")
		}
	}

	if err != nil {
		log.Err(c.logger, err, "receive cell error")
	}
	c.logger.Debug("exit read loop")
}

// cleanup cleans up resources related to the connection.
func (c *Connection) cleanup() error {
	c.logger.Info("cleaning up connection")

	// Close all circuit channels.
	c.channels.CloseAll()

	// BUG(mbm): waitgroup to make sure circuits complete any writes?

	// Unregister the connection.
	return c.router.connections.RemoveConnection(c)
}

// Close the connection.
func (c *Connection) Close() error {
	// BUG(mbm): graceful stop to runloop
	return multierr.Combine(
		c.cleanup(),
		c.tlsConn.Close(),
	)
}

// GenerateCircuitLink
func (c *Connection) GenerateCircuitLink() CircuitLink {
	id, ch := c.channels.New(c.outbound)
	return NewCircuitLink(id, NewLink(c, CellChan(ch)), c.channels)
}

// NewCircuitLink
func (c *Connection) NewCircuitLink(id CircID) (CircuitLink, error) {
	ch, err := c.channels.NewWithID(id)
	if err != nil {
		return nil, err
	}
	return NewCircuitLink(id, NewLink(c, CellChan(ch)), c.channels), nil
}

func CellLogger(l log.Logger, cell Cell) log.Logger {
	return l.With("cmd", cell.Command()).With("circid", cell.CircID())
}

// ChannelManager manages a collection of cell channels.
type ChannelManager struct {
	channels   map[CircID]chan Cell
	bufferSize int

	sync.RWMutex
}

func NewChannelManager(n int) *ChannelManager {
	return &ChannelManager{
		channels:   make(map[CircID]chan Cell),
		bufferSize: n,
	}
}

func (m *ChannelManager) New(outbound bool) (CircID, chan Cell) {
	m.Lock()
	defer m.Unlock()

	// Reference: https://github.com/torproject/torspec/blob/4074b891e53e8df951fc596ac6758d74da290c60/tor-spec.txt#L931-L933
	//
	//	   In link protocol version 4 or higher, whichever node initiated the
	//	   connection sets its MSB to 1, and whichever node didn't initiate the
	//	   connection sets its MSB to 0.
	//
	msb := uint32(0)
	if outbound {
		msb = uint32(1)
	}

	// BUG(mbm): potential infinite (or at least long) loop to find a new id
	for {
		id := GenerateCircID(msb)
		// 0 is reserved
		if id == 0 {
			continue
		}
		_, exists := m.channels[id]
		if exists {
			continue
		}
		ch := m.newWithID(id)
		return id, ch
	}
}

func (m *ChannelManager) NewWithID(id CircID) (chan Cell, error) {
	m.Lock()
	defer m.Unlock()
	_, exists := m.channels[id]
	if exists {
		return nil, errors.New("cannot override existing channel id")
	}
	return m.newWithID(id), nil
}

func (m *ChannelManager) newWithID(id CircID) chan Cell {
	ch := make(chan Cell, m.bufferSize)
	m.channels[id] = ch
	return ch
}

func (m *ChannelManager) Channel(id CircID) (chan Cell, bool) {
	m.RLock()
	defer m.RUnlock()
	ch, ok := m.channels[id]
	return ch, ok
}

func (m *ChannelManager) Close(id CircID) error {
	m.Lock()
	defer m.Unlock()

	ch, ok := m.channels[id]
	if !ok {
		return errors.New("unknown circuit")
	}

	close(ch)
	delete(m.channels, id)

	return nil
}

func (m *ChannelManager) CloseAll() {
	m.Lock()
	defer m.Unlock()

	for _, ch := range m.channels {
		close(ch)
	}

	m.channels = make(map[CircID]chan Cell)
}
<|endoftext|>


<commit_before>package app

import (
	"io/ioutil"
	"log"
	"net"
	"time"

	"golang.org/x/crypto/ssh"
)

const MAX_RETRIES_SERVER = 60 * 60
const MAX_RETRIES_CLIENT = 60 * 60

type GetServerReq struct {
	reply chan *ssh.Client
}

type ConnectionDone struct {
	client *ssh.Client
	err    error
}

// Conn wraps a net.Conn, and sets a deadline for every read
// and write operation.
type Conn struct {
	net.Conn
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
}

func (c *Conn) Read(b []byte) (int, error) {
	err := c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout))
	if err != nil {
		return 0, err
	}
	return c.Conn.Read(b)
}

func (c *Conn) Write(b []byte) (int, error) {
	err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
	if err != nil {
		return 0, err
	}
	return c.Conn.Write(b)
}

func directConnect(network, addr string, timeout time.Duration) (net.Conn, error) {
	conn, err := net.DialTimeout(network, addr, timeout)
	if err != nil {
		return nil, err
	}

	return &Conn{conn, timeout, timeout}, nil
}

func dialSSH(info *SSHTunnel, config *ssh.ClientConfig, proxyCommand string) (*ssh.Client, error) {
	var conn net.Conn
	var err error

	if proxyCommand == "" {
		conn, err = directConnect(`tcp`, info.Address, 5*time.Second)
	} else {
		conn, err = connectProxy(proxyCommand, info.Address)
	}
	if err != nil {
		return nil, err
	}
	c, chans, reqs, err := ssh.NewClientConn(conn, info.Address, config)
	if err != nil {
		return nil, err
	}
	return ssh.NewClient(c, chans, reqs), nil
}

func acceptAllHostKeys(hostname string, remote net.Addr, key ssh.PublicKey) error {
	return nil
}

func connectSSH(info PathInfo, resp chan<- *ssh.Client, progress chan<- ProgressCmd) {
	var err error
	log.Printf("SSH-connecting to %s\n", info.SSHTunnel.Address)

	progress <- ProgressCmd{"connection_start", nil}
	sshKey := []byte(info.SSHTunnel.SSHKeyContents)
	if info.SSHTunnel.SSHKeyFileName != "" {
		sshKey, err = ioutil.ReadFile(info.SSHTunnel.SSHKeyFileName)
		if err != nil {
			progress <- ProgressCmd{"connection_failed", "Failed to read SSH key"}
			resp <- nil
			return
		}
	}

	key, err := ssh.ParsePrivateKey(sshKey)
	if err != nil {
		progress <- ProgressCmd{"connection_failed", "Failed to parse SSH key"}
		resp <- nil
		return
	}

	config := &ssh.ClientConfig{
		User: info.SSHTunnel.Username,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(key),
		},
		HostKeyCallback: acceptAllHostKeys,
		Timeout:         10 * time.Second,
	}

	currentRetriesServer := 0
	var sshClientConn *ssh.Client

	for {
		progress <- ProgressCmd{"connection_try", nil}
		if sshClientConn, err = dialSSH(info.SSHTunnel, config, proxyCommand); err == nil {
			break
		}

		currentRetriesServer++
		log.Printf("SSH Connection failed %s: %s\n", info.SSHTunnel.Address, err.Error())

		if currentRetriesServer < (MAX_RETRIES_SERVER / 1) {
			log.Println(`Retry...`)
			progress <- ProgressCmd{"connection_retry", nil}
			time.Sleep(1 * time.Second)
		} else {
			log.Println(`SSH connection limit reached. Aborting`)
			progress <- ProgressCmd{"connection_failed", "Connection retry limit reached"}
			resp <- nil
			return
		}
	}
	progress <- ProgressCmd{"connection_established", nil}

	runBootstrap(sshClientConn, info, progress)

	if info.SSHTunnel.Run != nil {
		session, _ := sshClientConn.NewSession()

		modes := ssh.TerminalModes{
			ssh.ECHO: 0,
		}

		if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
			log.Fatalf("request for pseudo terminal failed: %s", err)
		}

		session.Start(info.SSHTunnel.Run.Command)
		time.Sleep(500 * time.Millisecond)
	}
	log.Printf("SSH-connection OK. Waiting for %s to be ready...\n", info.Backend.Address)

	progress <- ProgressCmd{"waiting_backend", nil}
	currentRetriesClient := 0
	for {
		log.Printf("Trying to connect to %s...\n", info.Backend.Address)
		if conn, err := sshClientConn.Dial("tcp", info.Backend.Address); err == nil {
			log.Printf("Connected to %s successfully!\n", info.Backend.Address)
			conn.Close()
			break
		}
		currentRetriesClient++

		if currentRetriesClient < (MAX_RETRIES_CLIENT / 5) {
			log.Printf("Failed to connect to %s - retrying...\n", info.Backend.Address)
			progress <- ProgressCmd{"waiting_backend_retry", nil}
			time.Sleep(5 * time.Second)
		} else {
			log.Printf("Connection limit to %s reached. Aborting.\n", info.Backend.Address)
			progress <- ProgressCmd{"waiting_backend_timeout", "Connection retry limit reached"}
			resp <- nil
			return
		}
	}

	progress <- ProgressCmd{"connection_success", nil}
	resp <- sshClientConn
}
<commit_msg>Logging specific errors on ssh client connection<commit_after>package app

import (
	"io/ioutil"
	"net"
	"time"

	"github.com/Sirupsen/logrus"

	"golang.org/x/crypto/ssh"
)

const MAX_RETRIES_SERVER = 60 * 60
const MAX_RETRIES_CLIENT = 60 * 60

type GetServerReq struct {
	reply chan *ssh.Client
}

type ConnectionDone struct {
	client *ssh.Client
	err    error
}

// Conn wraps a net.Conn, and sets a deadline for every read
// and write operation.
type Conn struct {
	net.Conn
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
}

func (c *Conn) Read(b []byte) (int, error) {
	err := c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout))
	if err != nil {
		return 0, err
	}
	return c.Conn.Read(b)
}

func (c *Conn) Write(b []byte) (int, error) {
	err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
	if err != nil {
		return 0, err
	}
	return c.Conn.Write(b)
}

func directConnect(network, addr string, timeout time.Duration) (net.Conn, error) {
	conn, err := net.DialTimeout(network, addr, timeout)
	if err != nil {
		return nil, err
	}

	return &Conn{conn, timeout, timeout}, nil
}

func dialSSH(info *SSHTunnel, config *ssh.ClientConfig, proxyCommand string) (*ssh.Client, error) {
	var conn net.Conn
	var err error

	if proxyCommand == "" {
		conn, err = directConnect(`tcp`, info.Address, 5*time.Second)
	} else {
		conn, err = connectProxy(proxyCommand, info.Address)
	}
	if err != nil {
		return nil, err
	}
	c, chans, reqs, err := ssh.NewClientConn(conn, info.Address, config)
	if err != nil {
		return nil, err
	}
	return ssh.NewClient(c, chans, reqs), nil
}

func acceptAllHostKeys(hostname string, remote net.Addr, key ssh.PublicKey) error {
	return nil
}

func connectSSH(info PathInfo, resp chan<- *ssh.Client, progress chan<- ProgressCmd) {
	var err error
	log := logrus.New().WithFields(logrus.Fields{
		"type": "ssh-server-conn",
		"host": info.Host,
		"path": info.Prefix,
	})

	log.Printf("SSH-connecting to %s\n", info.SSHTunnel.Address)

	progress <- ProgressCmd{"connection_start", nil}
	sshKey := []byte(info.SSHTunnel.SSHKeyContents)
	if info.SSHTunnel.SSHKeyFileName != "" {
		sshKey, err = ioutil.ReadFile(info.SSHTunnel.SSHKeyFileName)
		if err != nil {
			progress <- ProgressCmd{"connection_failed", "Failed to read SSH key"}
			resp <- nil
			return
		}
	}

	key, err := ssh.ParsePrivateKey(sshKey)
	if err != nil {
		progress <- ProgressCmd{"connection_failed", "Failed to parse SSH key"}
		resp <- nil
		return
	}

	config := &ssh.ClientConfig{
		User: info.SSHTunnel.Username,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(key),
		},
		HostKeyCallback: acceptAllHostKeys,
		Timeout:         10 * time.Second,
	}

	currentRetriesServer := 0
	var sshClientConn *ssh.Client

	for {
		progress <- ProgressCmd{"connection_try", nil}
		if sshClientConn, err = dialSSH(info.SSHTunnel, config, proxyCommand); err == nil {
			break
		}

		currentRetriesServer++
		log.Printf("SSH Connection failed %s: %s\n", info.SSHTunnel.Address, err.Error())

		if currentRetriesServer < (MAX_RETRIES_SERVER / 1) {
			log.Println(`Retry...`)
			progress <- ProgressCmd{"connection_retry", nil}
			time.Sleep(1 * time.Second)
		} else {
			log.Println(`SSH connection limit reached. Aborting`)
			progress <- ProgressCmd{"connection_failed", "Connection retry limit reached"}
			resp <- nil
			return
		}
	}
	progress <- ProgressCmd{"connection_established", nil}

	runBootstrap(sshClientConn, info, progress)

	if info.SSHTunnel.Run != nil {
		session, _ := sshClientConn.NewSession()

		modes := ssh.TerminalModes{
			ssh.ECHO: 0,
		}

		if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
			log.Fatalf("request for pseudo terminal failed: %s", err)
		}

		session.Start(info.SSHTunnel.Run.Command)
		time.Sleep(500 * time.Millisecond)
	}
	log.Printf("SSH-connection OK. Waiting for %s to be ready...\n", info.Backend.Address)

	progress <- ProgressCmd{"waiting_backend", nil}
	currentRetriesClient := 0
	for {
		log.Printf("Trying to connect to %s...\n", info.Backend.Address)
		if conn, err := sshClientConn.Dial("tcp", info.Backend.Address); err == nil {
			log.Printf("Connected to %s successfully!\n", info.Backend.Address)
			conn.Close()
			break
		}
		currentRetriesClient++

		if currentRetriesClient < (MAX_RETRIES_CLIENT / 5) {
			log.Printf("Failed to connect to %s - %v, retrying...\n", info.Backend.Address, err)
			progress <- ProgressCmd{"waiting_backend_retry", nil}
			time.Sleep(5 * time.Second)
		} else {
			log.Printf("Connection limit to %s reached. Aborting.\n", info.Backend.Address)
			progress <- ProgressCmd{"waiting_backend_timeout", "Connection retry limit reached"}
			resp <- nil
			return
		}
	}

	progress <- ProgressCmd{"connection_success", nil}
	resp <- sshClientConn
}
<|endoftext|>


<commit_before>package goPshdlRest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

var (
	// mux is the HTTP request multiplexer used with the test server.
	mux *http.ServeMux

	// client is the GitHub client being tested.
	client *Client

	// server is a test HTTP server used to provide mock API responses.
	server *httptest.Server
)

// setup sets up a test HTTP server along with a github.Client that is
// configured to talk to that test server.  Tests should register handlers on
// mux which provide mock responses for the API method being tested.
func setup() {
	// test server
	mux = http.NewServeMux()
	server = httptest.NewServer(mux)

	// github client configured to use test server
	client = NewClient(nil)
	url, _ := url.Parse(server.URL + "/api/v0.1/")
	client.BaseURL = url
	client.Workspace.Id = "1234"
}

// teardown closes the test HTTP server.
func teardown() {
	server.Close()
}

func testMethod(t *testing.T, r *http.Request, want string) {
	if want != r.Method {
		t.Errorf("Request method = %v, want %v", r.Method, want)
	}
}

type values map[string]string

func testFormValues(t *testing.T, r *http.Request, values values) {
	want := url.Values{}
	for k, v := range values {
		want.Add(k, v)
	}

	r.ParseForm()
	if !reflect.DeepEqual(want, r.Form) {
		t.Errorf("Request parameters = %v, want %v", r.Form, want)
	}
}

func testHeader(t *testing.T, r *http.Request, header string, want string) {
	if value := r.Header.Get(header); want != value {
		t.Errorf("Header %s = %s, want: %s", header, value, want)
	}
}

func testBody(t *testing.T, r *http.Request, want string) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		t.Errorf("Unable to read body")
	}
	str := string(b)
	if want != str {
		t.Errorf("Body = %s, want: %s", str, want)
	}
}

// Helper function to test that a value is marshalled to JSON as expected.
func testJSONMarshal(t *testing.T, v interface{}, want string) {
	j, err := json.Marshal(v)
	if err != nil {
		t.Errorf("Unable to marshal JSON for %v", v)
	}

	w := new(bytes.Buffer)
	err = json.Compact(w, []byte(want))
	if err != nil {
		t.Errorf("String is not valid json: %s", want)
	}

	if w.String() != string(j) {
		t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w)
	}

	// now go the other direction and make sure things unmarshal as expected
	u := reflect.ValueOf(v).Interface()
	if err := json.Unmarshal([]byte(want), u); err != nil {
		t.Errorf("Unable to unmarshal JSON for %v", want)
	}

	if !reflect.DeepEqual(v, u) {
		t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v)
	}
}

func TestNewClient(t *testing.T) {
	var c *Client
	Convey("Given a new Client", t, func() {
		c = NewClient(nil)

		Convey("It should have the correct BaseURL", func() {
			So(c.BaseURL.String(), ShouldEqual, defaultBaseURL)
		})

		Convey("It should have the correct UserAgent", func() {
			So(c.UserAgent, ShouldEqual, userAgent)
		})
	})
}

func TestNewRequest(t *testing.T) {
	var (
		c   *Client
		req *http.Request
	)

	type createPut struct {
		Name, Email string
	}

	Convey("Given a new Client", t, func() {
		c = NewClient(nil)
		Convey("Given a valid Request", func() {

			inURL, outURL := "foo", defaultBaseURL+"foo"
			inBody, outBody := &createPut{Name: "l", Email: "hi@me.com"}, `{"Name":"l","Email":"hi@me.com"}`+"\n"
			req, _ = c.NewRequest("PUT", inURL, inBody)

			Convey("It should have its URL expanded", func() {
				So(req.URL.String(), ShouldEqual, outURL)
			})

			Convey("It should encode the body in JSON", func() {
				body, _ := ioutil.ReadAll(req.Body)
				So(string(body), ShouldEqual, outBody)
			})

			Convey("It should have the default user-agent is attached to the request", func() {
				userAgent := req.Header.Get("User-Agent")
				So(c.UserAgent, ShouldEqual, userAgent)
			})

		})

		Convey("Given an invalid Request", func() {
			type T struct {
				A map[int]interface{}
			}
			_, err := c.NewRequest("GET", "/", &T{})

			Convey("It should return an error (beeing *json.UnsupportedTypeError)", func() {
				So(err, ShouldNotBeNil)
				So(err, ShouldHaveSameTypeAs, &json.UnsupportedTypeError{})
			})

		})

		Convey("Given a bad Request URL", func() {
			_, err := c.NewRequest("GET", ":", nil)
			Convey("It should return an error (beeing *url.Error{})", func() {
				So(err, ShouldNotBeNil)
				So(err, ShouldHaveSameTypeAs, &url.Error{})
			})
		})
	})

}

func TestDo(t *testing.T) {

	Convey("Given a clean test server", t, func() {
		setup()

		Convey("Do() should send the request", func() {

			type foo struct {
				A string
			}

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				So(r.Method, ShouldEqual, "GET")

				fmt.Fprint(w, `{"A":"a"}`)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			body := new(foo)
			client.Do(req, body)

			So(body, ShouldResemble, &foo{"a"})
		})

		Convey("A Bad Request should return an error", func() {

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "Bad Request", 400)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			_, err := client.Do(req, nil)
			So(err, ShouldNotBeNil)

		})

		Convey("A plain request should get response", func() {

			want := `/api/v0.1/servertime`

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				So(r.Method, ShouldEqual, "GET")
				fmt.Fprint(w, want)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			resp, _, _ := client.DoPlain(req)

			body := string(resp)
			So(body, ShouldEqual, want)
		})

		Convey("A bad plain request should return a http error", func() {

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "Bad Request", 400)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			_, _, err := client.DoPlain(req)
			So(err, ShouldNotBeNil)
		})

		Reset(teardown)
	})

}
<commit_msg>better description<commit_after>package goPshdlRest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

var (
	// mux is the HTTP request multiplexer used with the test server.
	mux *http.ServeMux

	// client is the GitHub client being tested.
	client *Client

	// server is a test HTTP server used to provide mock API responses.
	server *httptest.Server
)

// setup sets up a test HTTP server along with a github.Client that is
// configured to talk to that test server.  Tests should register handlers on
// mux which provide mock responses for the API method being tested.
func setup() {
	// test server
	mux = http.NewServeMux()
	server = httptest.NewServer(mux)

	// github client configured to use test server
	client = NewClient(nil)
	url, _ := url.Parse(server.URL + "/api/v0.1/")
	client.BaseURL = url
	client.Workspace.Id = "1234"
}

// teardown closes the test HTTP server.
func teardown() {
	server.Close()
}

func testMethod(t *testing.T, r *http.Request, want string) {
	if want != r.Method {
		t.Errorf("Request method = %v, want %v", r.Method, want)
	}
}

type values map[string]string

func testFormValues(t *testing.T, r *http.Request, values values) {
	want := url.Values{}
	for k, v := range values {
		want.Add(k, v)
	}

	r.ParseForm()
	if !reflect.DeepEqual(want, r.Form) {
		t.Errorf("Request parameters = %v, want %v", r.Form, want)
	}
}

func testHeader(t *testing.T, r *http.Request, header string, want string) {
	if value := r.Header.Get(header); want != value {
		t.Errorf("Header %s = %s, want: %s", header, value, want)
	}
}

func testBody(t *testing.T, r *http.Request, want string) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		t.Errorf("Unable to read body")
	}
	str := string(b)
	if want != str {
		t.Errorf("Body = %s, want: %s", str, want)
	}
}

// Helper function to test that a value is marshalled to JSON as expected.
func testJSONMarshal(t *testing.T, v interface{}, want string) {
	j, err := json.Marshal(v)
	if err != nil {
		t.Errorf("Unable to marshal JSON for %v", v)
	}

	w := new(bytes.Buffer)
	err = json.Compact(w, []byte(want))
	if err != nil {
		t.Errorf("String is not valid json: %s", want)
	}

	if w.String() != string(j) {
		t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w)
	}

	// now go the other direction and make sure things unmarshal as expected
	u := reflect.ValueOf(v).Interface()
	if err := json.Unmarshal([]byte(want), u); err != nil {
		t.Errorf("Unable to unmarshal JSON for %v", want)
	}

	if !reflect.DeepEqual(v, u) {
		t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v)
	}
}

func TestNewClient(t *testing.T) {
	var c *Client
	Convey("Given a new Client", t, func() {
		c = NewClient(nil)

		Convey("It should have the correct BaseURL", func() {
			So(c.BaseURL.String(), ShouldEqual, defaultBaseURL)
		})

		Convey("It should have the correct UserAgent", func() {
			So(c.UserAgent, ShouldEqual, userAgent)
		})
	})
}

func TestNewRequest(t *testing.T) {
	var (
		c   *Client
		req *http.Request
	)

	type createPut struct {
		Name, Email string
	}

	Convey("Given a new Client", t, func() {
		c = NewClient(nil)

		Convey("and a valid Request", func() {
			inURL, outURL := "foo", defaultBaseURL+"foo"
			inBody, outBody := &createPut{Name: "l", Email: "hi@me.com"}, `{"Name":"l","Email":"hi@me.com"}`+"\n"
			req, _ = c.NewRequest("PUT", inURL, inBody)

			Convey("It should have its URL expanded", func() {
				So(req.URL.String(), ShouldEqual, outURL)
			})

			Convey("It should encode the body in JSON", func() {
				body, _ := ioutil.ReadAll(req.Body)
				So(string(body), ShouldEqual, outBody)
			})

			Convey("It should have the default user-agent is attached to the request", func() {
				userAgent := req.Header.Get("User-Agent")
				So(c.UserAgent, ShouldEqual, userAgent)
			})

		})

		Convey("and an invalid Request", func() {
			type T struct {
				A map[int]interface{}
			}
			_, err := c.NewRequest("GET", "/", &T{})

			Convey("It should return an error (beeing *json.UnsupportedTypeError)", func() {
				So(err, ShouldNotBeNil)
				So(err, ShouldHaveSameTypeAs, &json.UnsupportedTypeError{})
			})

		})

		Convey("and a bad Request URL", func() {
			_, err := c.NewRequest("GET", ":", nil)
			Convey("It should return an error (beeing *url.Error{})", func() {
				So(err, ShouldNotBeNil)
				So(err, ShouldHaveSameTypeAs, &url.Error{})
			})
		})
	})

}

func TestDo(t *testing.T) {

	Convey("Given a clean test server", t, func() {
		setup()

		Convey("Do() should send the request", func() {

			type foo struct {
				A string
			}

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				So(r.Method, ShouldEqual, "GET")

				fmt.Fprint(w, `{"A":"a"}`)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			body := new(foo)
			client.Do(req, body)

			So(body, ShouldResemble, &foo{"a"})
		})

		Convey("A Bad Request should return an error", func() {

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "Bad Request", 400)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			_, err := client.Do(req, nil)
			So(err, ShouldNotBeNil)

		})

		Convey("A plain request should get response", func() {

			want := `/api/v0.1/servertime`

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				So(r.Method, ShouldEqual, "GET")
				fmt.Fprint(w, want)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			resp, _, _ := client.DoPlain(req)

			body := string(resp)
			So(body, ShouldEqual, want)
		})

		Convey("A bad plain request should return a http error", func() {

			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "Bad Request", 400)
			})

			req, _ := client.NewRequest("GET", "/", nil)
			_, _, err := client.DoPlain(req)
			So(err, ShouldNotBeNil)
		})

		Reset(teardown)
	})

}
<|endoftext|>