func onlyIDQuery(q *protocol.KontrolQuery) bool {
	fields := q.Fields()
	for _, k := range keyOrder {
		v := fields[k]
		if k != "id" && v != "" {
			return false
		}
	}
	if fields["id"] != "" {
		return true
	}
	return false
}
onlyIDQuery returns true if the query contains only a non-empty ID and all
others keys are empty
package kontrol
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"
	etcd "github.com/coreos/etcd/client"
	"github.com/hashicorp/go-version"
	"github.com/koding/kite"
	kontrolprotocol "github.com/koding/kite/kontrol/protocol"
	"github.com/koding/kite/protocol"
)
var keyOrder = []string{
	"username",
	"environment",
	"name",
	"version",
	"region",
	"hostname",
	"id",
}
type Etcd struct {
	client etcd.KeysAPI
	log    kite.Logger
}
func NewEtcd(machines []string, log kite.Logger) *Etcd {
	if machines == nil || len(machines) == 0 {
		machines = []string{"http://127.0.0.1:2379"}
	}
	cfg := etcd.Config{
		Endpoints:               machines,
		Transport:               etcd.DefaultTransport,
		HeaderTimeoutPerRequest: time.Second,
	}
	client, err := etcd.New(cfg)
	if err != nil {
		panic("cannot connect to etcd cluster: " + strings.Join(machines, ","))
	}
	e := &Etcd{
		client: NewKeysAPILogger(etcd.NewKeysAPI(client), log),
		log:    log,
	}
	_, err = e.client.Get(context.Background(), KitesPrefix, nil)
	if err == nil {
		return e
	}
	_, err = e.client.Set(
		context.Background(),
		KitesPrefix,
		"",
		&etcd.SetOptions{
			Dir:       true,
			PrevExist: etcd.PrevIgnore,
		},
	)
	if err != nil {
		log.Fatal("Could not create KitesPrefix %q: %v", KitesPrefix, err)
	}
	return e
}
func (e *Etcd) Delete(k *protocol.Kite) error {
	etcdKey := KitesPrefix + k.String()
	etcdIDKey := KitesPrefix + "/" + k.ID
	_, e1 := e.client.Delete(context.TODO(), etcdKey, &etcd.DeleteOptions{
		Recursive: true,
	})
	_, e2 := e.client.Delete(context.TODO(), etcdIDKey, &etcd.DeleteOptions{
		Recursive: true,
	})
	return nonil(e1, e2)
}
func (e *Etcd) Clear() error {
	_, err := e.client.Delete(context.TODO(), KitesPrefix, &etcd.DeleteOptions{
		Recursive: true,
	})
	return err
}
func (e *Etcd) Upsert(k *protocol.Kite, v *kontrolprotocol.RegisterValue) error {
	return e.Add(k, v)
}
func (e *Etcd) Add(k *protocol.Kite, v *kontrolprotocol.RegisterValue) error {
	etcdKey := KitesPrefix + k.String()
	etcdIDKey := KitesPrefix + "/" + k.ID
	p, err := json.Marshal(v)
	if err != nil {
		return err
	}
	value := string(p)
	_, err = e.client.Set(context.TODO(),
		etcdKey,
		value,
		&etcd.SetOptions{
			TTL:       KeyTTL,
			PrevExist: etcd.PrevIgnore,
		},
	)
	if err != nil {
		return err
	}
	_, err = e.client.Set(context.TODO(),
		etcdIDKey,
		value,
		&etcd.SetOptions{
			TTL:       KeyTTL,
			PrevExist: etcd.PrevIgnore,
		},
	)
	return err
}
func (e *Etcd) Update(k *protocol.Kite, v *kontrolprotocol.RegisterValue) error {
	etcdKey := KitesPrefix + k.String()
	etcdIDKey := KitesPrefix + "/" + k.ID
	p, err := json.Marshal(v)
	if err != nil {
		return err
	}
	value := string(p)
	_, err = e.client.Set(context.TODO(),
		etcdKey,
		value,
		&etcd.SetOptions{
			TTL:       KeyTTL,
			PrevExist: etcd.PrevExist,
		},
	)
	if err != nil {
		return e.Add(k, v)
	}
	_, err = e.client.Set(context.TODO(),
		etcdIDKey,
		value,
		&etcd.SetOptions{
			TTL:       KeyTTL,
			PrevExist: etcd.PrevExist,
		},
	)
	if err != nil {
		return err
	}
	_, err = e.client.Set(context.TODO(),
		KitesPrefix+"/"+k.Username,
		"",
		&etcd.SetOptions{
			TTL:       KeyTTL,
			PrevExist: etcd.PrevExist,
		},
	)
	return err
}
func (e *Etcd) Get(query *protocol.KontrolQuery) (Kites, error) {
	etcdKey, err := e.etcdKey(query)
	if err != nil {
		return nil, err
	}
	var hasVersionConstraint bool  
	var keyRest string             
	var versionConstraint version.Constraints
	_, err = version.NewVersion(query.Version)
	if err != nil && query.Version != "" {
		versionConstraint, err = version.NewConstraint(query.Version)
		if err != nil {
			return nil, err
		}
		hasVersionConstraint = true
		nameQuery := &protocol.KontrolQuery{
			Username:    query.Username,
			Environment: query.Environment,
			Name:        query.Name,
		}
		etcdKey, _ = GetQueryKey(nameQuery)
		keyRest = "/" + strings.TrimRight(
			query.Region+"/"+query.Hostname+"/"+query.ID, "/")
	}
	resp, err := e.client.Get(context.TODO(),
		KitesPrefix+"/"+etcdKey,
		&etcd.GetOptions{
			Recursive: true,
		},
	)
	if err != nil {
		return nil, err
	}
	kites := make(Kites, 0)
	node := NewNode(resp.Node)
	if node.HasValue() {
		oneKite, err := node.Kite()
		if err != nil {
			return nil, err
		}
		kites = append(kites, oneKite)
	} else {
		kites, err = node.Kites()
		if err != nil {
			return nil, err
		}
		if hasVersionConstraint {
			kites.Filter(versionConstraint, keyRest)
		}
	}
	kites.Shuffle()
	return kites, nil
}
func (e *Etcd) etcdKey(query *protocol.KontrolQuery) (string, error) {
	if onlyIDQuery(query) {
		resp, err := e.client.Get(context.TODO(),
			KitesPrefix+"/"+query.ID,
			&etcd.GetOptions{
				Recursive: true,
			},
		)
		if err != nil {
			return "", err
		}
		return resp.Node.Value, nil
	}
	return GetQueryKey(query)
}
type RegisterValue struct {
	URL string `json:"url"`
}
func validateKiteKey(k *protocol.Kite) error {
	fields := k.Query().Fields()
	for k, v := range fields {
		if v == "" {
			return fmt.Errorf("Empty Kite field: %s", k)
		}
		if strings.ContainsRune(v, '/') {
			return fmt.Errorf("Field \"%s\" must not contain '/'", k)
		}
	}
	return nil
}