mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
commit
b8cefd39c9
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
src
|
||||
etcd
|
||||
src/
|
||||
pkg/
|
||||
./etcd
|
||||
release_version.go
|
||||
|
8
.travis.yml
Normal file
8
.travis.yml
Normal file
@ -0,0 +1,8 @@
|
||||
language: go
|
||||
go: 1.1
|
||||
|
||||
install:
|
||||
- echo "Skip install"
|
||||
|
||||
script:
|
||||
- ./test
|
@ -1,5 +1,7 @@
|
||||
# etcd
|
||||
|
||||
[](https://travis-ci.org/coreos/etcd)
|
||||
|
||||
A highly-available key value store for shared configuration and service discovery. etcd is inspired by zookeeper and doozer, with a focus on:
|
||||
|
||||
* Simple: curl'able user facing API (HTTP+JSON)
|
||||
|
7
test
7
test
@ -1,3 +1,8 @@
|
||||
#!/bin/sh
|
||||
go build
|
||||
|
||||
# Get GOPATH, etc from build
|
||||
. ./build
|
||||
|
||||
# Run the tests!
|
||||
go test -i
|
||||
go test -v
|
||||
|
@ -377,12 +377,17 @@ func (es enumSymbol) GenerateAlias(g *Generator, pkg string) {
|
||||
}
|
||||
|
||||
type constOrVarSymbol struct {
|
||||
sym string
|
||||
typ string // either "const" or "var"
|
||||
sym string
|
||||
typ string // either "const" or "var"
|
||||
cast string // if non-empty, a type cast is required (used for enums)
|
||||
}
|
||||
|
||||
func (cs constOrVarSymbol) GenerateAlias(g *Generator, pkg string) {
|
||||
g.P(cs.typ, " ", cs.sym, " = ", pkg, ".", cs.sym)
|
||||
v := pkg + "." + cs.sym
|
||||
if cs.cast != "" {
|
||||
v = cs.cast + "(" + v + ")"
|
||||
}
|
||||
g.P(cs.typ, " ", cs.sym, " = ", v)
|
||||
}
|
||||
|
||||
// Object is an interface abstracting the abilities shared by enums, messages, extensions and imported objects.
|
||||
@ -1157,7 +1162,7 @@ func (g *Generator) generateEnum(enum *EnumDescriptor) {
|
||||
|
||||
name := ccPrefix + *e.Name
|
||||
g.P(name, " ", ccTypeName, " = ", e.Number)
|
||||
g.file.addExport(enum, constOrVarSymbol{name, "const"})
|
||||
g.file.addExport(enum, constOrVarSymbol{name, "const", ccTypeName})
|
||||
}
|
||||
g.Out()
|
||||
g.P(")")
|
||||
@ -1255,9 +1260,18 @@ func (g *Generator) goTag(field *descriptor.FieldDescriptorProto, wiretype strin
|
||||
case descriptor.FieldDescriptorProto_TYPE_ENUM:
|
||||
// For enums we need to provide the integer constant.
|
||||
obj := g.ObjectNamed(field.GetTypeName())
|
||||
if id, ok := obj.(*ImportedDescriptor); ok {
|
||||
// It is an enum that was publicly imported.
|
||||
// We need the underlying type.
|
||||
obj = id.o
|
||||
}
|
||||
enum, ok := obj.(*EnumDescriptor)
|
||||
if !ok {
|
||||
g.Fail("enum type inconsistent for", CamelCaseSlice(obj.TypeName()))
|
||||
log.Printf("obj is a %T", obj)
|
||||
if id, ok := obj.(*ImportedDescriptor); ok {
|
||||
log.Printf("id.o is a %T", id.o)
|
||||
}
|
||||
g.Fail("unknown enum type", CamelCaseSlice(obj.TypeName()))
|
||||
}
|
||||
defaultValue = enum.integerValueAsString(defaultValue)
|
||||
}
|
||||
@ -1268,6 +1282,9 @@ func (g *Generator) goTag(field *descriptor.FieldDescriptorProto, wiretype strin
|
||||
// We avoid using obj.PackageName(), because we want to use the
|
||||
// original (proto-world) package name.
|
||||
obj := g.ObjectNamed(field.GetTypeName())
|
||||
if id, ok := obj.(*ImportedDescriptor); ok {
|
||||
obj = id.o
|
||||
}
|
||||
enum = ",enum="
|
||||
if pkg := obj.File().GetPackage(); pkg != "" {
|
||||
enum += pkg + "."
|
||||
@ -1541,15 +1558,21 @@ func (g *Generator) generateMessage(message *Descriptor) {
|
||||
case *field.Type == descriptor.FieldDescriptorProto_TYPE_ENUM:
|
||||
// Must be an enum. Need to construct the prefixed name.
|
||||
obj := g.ObjectNamed(field.GetTypeName())
|
||||
enum, ok := obj.(*EnumDescriptor)
|
||||
if !ok {
|
||||
log.Print("don't know how to generate constant for", fieldname)
|
||||
var enum *EnumDescriptor
|
||||
if id, ok := obj.(*ImportedDescriptor); ok {
|
||||
// The enum type has been publicly imported.
|
||||
enum, _ = id.o.(*EnumDescriptor)
|
||||
} else {
|
||||
enum, _ = obj.(*EnumDescriptor)
|
||||
}
|
||||
if enum == nil {
|
||||
log.Printf("don't know how to generate constant for %s", fieldname)
|
||||
continue
|
||||
}
|
||||
def = g.DefaultPackageName(enum) + enum.prefix() + def
|
||||
def = g.DefaultPackageName(obj) + enum.prefix() + def
|
||||
}
|
||||
g.P(kind, fieldname, " ", typename, " = ", def)
|
||||
g.file.addExport(message, constOrVarSymbol{fieldname, kind})
|
||||
g.file.addExport(message, constOrVarSymbol{fieldname, kind, ""})
|
||||
}
|
||||
g.P()
|
||||
|
||||
@ -1701,7 +1724,7 @@ func (g *Generator) generateExtension(ext *ExtensionDescriptor) {
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.file.addExport(ext, constOrVarSymbol{ccTypeName, "var"})
|
||||
g.file.addExport(ext, constOrVarSymbol{ccTypeName, "var", ""})
|
||||
}
|
||||
|
||||
func (g *Generator) generateInitFunction() {
|
||||
|
@ -33,3 +33,8 @@ package imp;
|
||||
message PubliclyImportedMessage {
|
||||
optional int64 field = 1;
|
||||
}
|
||||
|
||||
enum PubliclyImportedEnum {
|
||||
GLASSES = 1;
|
||||
HAIR = 2;
|
||||
}
|
||||
|
@ -66,9 +66,10 @@ message Request {
|
||||
optional int32 group_field = 9;
|
||||
}
|
||||
|
||||
// This foreign message type is in imp2.proto,
|
||||
// These foreign types are in imp2.proto,
|
||||
// which is publicly imported by imp.proto.
|
||||
// optional imp.PubliclyImportedMessage pub = 10;
|
||||
// optional imp.PubliclyImportedEnum pub_enum = 13 [default=HAIR];
|
||||
|
||||
|
||||
optional int32 reset = 12;
|
||||
|
@ -18,10 +18,13 @@ package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var commentPrefix = []string{"//", "#", ";"}
|
||||
|
||||
func Read(filename string) (map[string]string, error) {
|
||||
var res = map[string]string{}
|
||||
in, err := os.Open(filename)
|
||||
@ -30,11 +33,19 @@ func Read(filename string) (map[string]string, error) {
|
||||
}
|
||||
scanner := bufio.NewScanner(in)
|
||||
line := ""
|
||||
section := ""
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "//") {
|
||||
if scanner.Text() == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(scanner.Text(), "#") {
|
||||
if line == "" {
|
||||
sec := checkSection(scanner.Text())
|
||||
if sec != "" {
|
||||
section = sec + "."
|
||||
continue
|
||||
}
|
||||
}
|
||||
if checkComment(scanner.Text()) {
|
||||
continue
|
||||
}
|
||||
line += scanner.Text()
|
||||
@ -42,13 +53,47 @@ func Read(filename string) (map[string]string, error) {
|
||||
line = line[:len(line)-1]
|
||||
continue
|
||||
}
|
||||
sp := strings.SplitN(line, "=", 2)
|
||||
if len(sp) != 2 {
|
||||
continue
|
||||
key, value, err := checkLine(line)
|
||||
if err != nil {
|
||||
return res, errors.New("WRONG: " + line)
|
||||
}
|
||||
res[strings.TrimSpace(sp[0])] = strings.TrimSpace(sp[1])
|
||||
res[section+key] = value
|
||||
line = ""
|
||||
}
|
||||
in.Close()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func checkSection(line string) string {
|
||||
line = strings.TrimSpace(line)
|
||||
lineLen := len(line)
|
||||
if lineLen < 2 {
|
||||
return ""
|
||||
}
|
||||
if line[0] == '[' && line[lineLen-1] == ']' {
|
||||
return line[1 : lineLen-1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkLine(line string) (string, string, error) {
|
||||
key := ""
|
||||
value := ""
|
||||
sp := strings.SplitN(line, "=", 2)
|
||||
if len(sp) != 2 {
|
||||
return key, value, errors.New("WRONG: " + line)
|
||||
}
|
||||
key = strings.TrimSpace(sp[0])
|
||||
value = strings.TrimSpace(sp[1])
|
||||
return key, value, nil
|
||||
}
|
||||
|
||||
func checkComment(line string) bool {
|
||||
line = strings.TrimSpace(line)
|
||||
for p := range commentPrefix {
|
||||
if strings.HasPrefix(line, commentPrefix[p]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -5,3 +5,6 @@ cc = dd, 2 ejkl ijfadjfl
|
||||
# 12jfiahdoif
|
||||
dd = c \
|
||||
oadi
|
||||
|
||||
[test]
|
||||
a = c c d
|
||||
|
241
third_party/github.com/coreos/go-etcd/etcd/client.go
vendored
Normal file
241
third_party/github.com/coreos/go-etcd/etcd/client.go
vendored
Normal file
@ -0,0 +1,241 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HTTP = iota
|
||||
HTTPS
|
||||
)
|
||||
|
||||
type Cluster struct {
|
||||
Leader string
|
||||
Machines []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
Scheme string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
cluster Cluster
|
||||
config Config
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// Setup a basic conf and cluster
|
||||
func NewClient() *Client {
|
||||
|
||||
// default leader and machines
|
||||
cluster := Cluster{
|
||||
Leader: "0.0.0.0:4001",
|
||||
Machines: make([]string, 1),
|
||||
}
|
||||
cluster.Machines[0] = "0.0.0.0:4001"
|
||||
|
||||
config := Config{
|
||||
// default use http
|
||||
Scheme: "http",
|
||||
// default timeout is one second
|
||||
Timeout: time.Second,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
Dial: dialTimeout,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
|
||||
return &Client{
|
||||
cluster: cluster,
|
||||
config: config,
|
||||
httpClient: &http.Client{Transport: tr},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) SetCertAndKey(cert string, key string) (bool, error) {
|
||||
|
||||
if cert != "" && key != "" {
|
||||
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Dial: dialTimeout,
|
||||
}
|
||||
|
||||
c.httpClient = &http.Client{Transport: tr}
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.New("Require both cert and key path")
|
||||
}
|
||||
|
||||
func (c *Client) SetScheme(scheme int) (bool, error) {
|
||||
if scheme == HTTP {
|
||||
c.config.Scheme = "http"
|
||||
return true, nil
|
||||
}
|
||||
if scheme == HTTPS {
|
||||
c.config.Scheme = "https"
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.New("Unknown Scheme")
|
||||
}
|
||||
|
||||
// Try to sync from the given machine
|
||||
func (c *Client) SetCluster(machines []string) bool {
|
||||
success := c.internalSyncCluster(machines)
|
||||
return success
|
||||
}
|
||||
|
||||
// sycn cluster information using the existing machine list
|
||||
func (c *Client) SyncCluster() bool {
|
||||
success := c.internalSyncCluster(c.cluster.Machines)
|
||||
return success
|
||||
}
|
||||
|
||||
// sync cluster information by providing machine list
|
||||
func (c *Client) internalSyncCluster(machines []string) bool {
|
||||
for _, machine := range machines {
|
||||
httpPath := c.createHttpPath(machine, "machines")
|
||||
resp, err := c.httpClient.Get(httpPath)
|
||||
if err != nil {
|
||||
// try another machine in the cluster
|
||||
continue
|
||||
} else {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// try another machine in the cluster
|
||||
continue
|
||||
}
|
||||
// update Machines List
|
||||
c.cluster.Machines = strings.Split(string(b), ",")
|
||||
logger.Debug("sync.machines ", c.cluster.Machines)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// serverName should contain both hostName and port
|
||||
func (c *Client) createHttpPath(serverName string, _path string) string {
|
||||
httpPath := path.Join(serverName, _path)
|
||||
httpPath = c.config.Scheme + "://" + httpPath
|
||||
return httpPath
|
||||
}
|
||||
|
||||
// Dial with timeout.
|
||||
func dialTimeout(network, addr string) (net.Conn, error) {
|
||||
return net.DialTimeout(network, addr, time.Second)
|
||||
}
|
||||
|
||||
func (c *Client) getHttpPath(s ...string) string {
|
||||
httpPath := path.Join(c.cluster.Leader, version)
|
||||
|
||||
for _, seg := range s {
|
||||
httpPath = path.Join(httpPath, seg)
|
||||
}
|
||||
|
||||
httpPath = c.config.Scheme + "://" + httpPath
|
||||
return httpPath
|
||||
}
|
||||
|
||||
func (c *Client) updateLeader(httpPath string) {
|
||||
// httpPath http://127.0.0.1:4001/v1...
|
||||
leader := strings.Split(httpPath, "://")[1]
|
||||
// we want to have 127.0.0.1:4001
|
||||
|
||||
leader = strings.Split(leader, "/")[0]
|
||||
logger.Debugf("update.leader[%s,%s]", c.cluster.Leader, leader)
|
||||
c.cluster.Leader = leader
|
||||
}
|
||||
|
||||
// Wrap GET, POST and internal error handling
|
||||
func (c *Client) sendRequest(method string, _path string, body string) (*http.Response, error) {
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
var req *http.Request
|
||||
|
||||
retry := 0
|
||||
// if we connect to a follower, we will retry until we found a leader
|
||||
for {
|
||||
|
||||
httpPath := c.getHttpPath(_path)
|
||||
logger.Debug("send.request.to ", httpPath)
|
||||
if body == "" {
|
||||
|
||||
req, _ = http.NewRequest(method, httpPath, nil)
|
||||
|
||||
} else {
|
||||
req, _ = http.NewRequest(method, httpPath, strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||||
}
|
||||
|
||||
resp, err = c.httpClient.Do(req)
|
||||
|
||||
logger.Debug("recv.response.from ", httpPath)
|
||||
// network error, change a machine!
|
||||
if err != nil {
|
||||
retry++
|
||||
if retry > 2*len(c.cluster.Machines) {
|
||||
return nil, errors.New("Cannot reach servers")
|
||||
}
|
||||
num := retry % len(c.cluster.Machines)
|
||||
logger.Debug("update.leader[", c.cluster.Leader, ",", c.cluster.Machines[num], "]")
|
||||
c.cluster.Leader = c.cluster.Machines[num]
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
continue
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if resp.StatusCode == http.StatusTemporaryRedirect {
|
||||
httpPath := resp.Header.Get("Location")
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if httpPath == "" {
|
||||
return nil, errors.New("Cannot get redirection location")
|
||||
}
|
||||
|
||||
c.updateLeader(httpPath)
|
||||
logger.Debug("send.redirect")
|
||||
// try to connect the leader
|
||||
continue
|
||||
} else if resp.StatusCode == http.StatusInternalServerError {
|
||||
retry++
|
||||
if retry > 2*len(c.cluster.Machines) {
|
||||
return nil, errors.New("Cannot reach servers")
|
||||
}
|
||||
resp.Body.Close()
|
||||
continue
|
||||
} else {
|
||||
logger.Debug("send.return.response ", httpPath)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
logger.Debug("error.from ", httpPath, " ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
38
third_party/github.com/coreos/go-etcd/etcd/client_test.go
vendored
Normal file
38
third_party/github.com/coreos/go-etcd/etcd/client_test.go
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// To pass this test, we need to create a cluster of 3 machines
|
||||
// The server should be listening on 127.0.0.1:4001, 4002, 4003
|
||||
func TestSync(t *testing.T) {
|
||||
fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003")
|
||||
|
||||
c := NewClient()
|
||||
|
||||
success := c.SyncCluster()
|
||||
if !success {
|
||||
t.Fatal("cannot sync machines")
|
||||
}
|
||||
|
||||
badMachines := []string{"abc", "edef"}
|
||||
|
||||
success = c.SetCluster(badMachines)
|
||||
|
||||
if success {
|
||||
t.Fatal("should not sync on bad machines")
|
||||
}
|
||||
|
||||
goodMachines := []string{"127.0.0.1:4002"}
|
||||
|
||||
success = c.SetCluster(goodMachines)
|
||||
|
||||
if !success {
|
||||
t.Fatal("cannot sync machines")
|
||||
} else {
|
||||
fmt.Println(c.cluster.Machines)
|
||||
}
|
||||
|
||||
}
|
19
third_party/github.com/coreos/go-etcd/etcd/debug.go
vendored
Normal file
19
third_party/github.com/coreos/go-etcd/etcd/debug.go
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"github.com/ccding/go-logging/logging"
|
||||
)
|
||||
|
||||
var logger, _ = logging.SimpleLogger("go-etcd")
|
||||
|
||||
func init() {
|
||||
logger.SetLevel(logging.FATAL)
|
||||
}
|
||||
|
||||
func OpenDebug() {
|
||||
logger.SetLevel(logging.NOTSET)
|
||||
}
|
||||
|
||||
func CloseDebug() {
|
||||
logger.SetLevel(logging.FATAL)
|
||||
}
|
41
third_party/github.com/coreos/go-etcd/etcd/delete.go
vendored
Normal file
41
third_party/github.com/coreos/go-etcd/etcd/delete.go
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/coreos/etcd/store"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (c *Client) Delete(key string) (*store.Response, error) {
|
||||
|
||||
resp, err := c.sendRequest("DELETE", path.Join("keys", key), "")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, handleError(b)
|
||||
}
|
||||
|
||||
var result store.Response
|
||||
|
||||
err = json.Unmarshal(b, &result)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
|
||||
}
|
22
third_party/github.com/coreos/go-etcd/etcd/delete_test.go
vendored
Normal file
22
third_party/github.com/coreos/go-etcd/etcd/delete_test.go
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
|
||||
c := NewClient()
|
||||
|
||||
c.Set("foo", "bar", 100)
|
||||
result, err := c.Delete("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result.PrevValue != "bar" || result.Value != "" {
|
||||
t.Fatalf("Delete failed with %s %s", result.PrevValue,
|
||||
result.Value)
|
||||
}
|
||||
|
||||
}
|
24
third_party/github.com/coreos/go-etcd/etcd/error.go
vendored
Normal file
24
third_party/github.com/coreos/go-etcd/etcd/error.go
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EtcdError struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause,omitempty"`
|
||||
}
|
||||
|
||||
func (e EtcdError) Error() string {
|
||||
return fmt.Sprintf("%d: %s (%s)", e.ErrorCode, e.Message, e.Cause)
|
||||
}
|
||||
|
||||
func handleError(b []byte) error {
|
||||
var err EtcdError
|
||||
|
||||
json.Unmarshal(b, &err)
|
||||
|
||||
return err
|
||||
}
|
83
third_party/github.com/coreos/go-etcd/etcd/get.go
vendored
Normal file
83
third_party/github.com/coreos/go-etcd/etcd/get.go
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/coreos/etcd/store"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (c *Client) Get(key string) ([]*store.Response, error) {
|
||||
logger.Debugf("get %s [%s]", key, c.cluster.Leader)
|
||||
resp, err := c.sendRequest("GET", path.Join("keys", key), "")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
|
||||
return nil, handleError(b)
|
||||
}
|
||||
|
||||
return convertGetResponse(b)
|
||||
|
||||
}
|
||||
|
||||
// GetTo gets the value of the key from a given machine address.
|
||||
// If the given machine is not available it returns an error.
|
||||
// Mainly use for testing purpose
|
||||
func (c *Client) GetFrom(key string, addr string) ([]*store.Response, error) {
|
||||
httpPath := c.createHttpPath(addr, path.Join(version, "keys", key))
|
||||
|
||||
resp, err := c.httpClient.Get(httpPath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, handleError(b)
|
||||
}
|
||||
|
||||
return convertGetResponse(b)
|
||||
}
|
||||
|
||||
// Convert byte stream to response.
|
||||
func convertGetResponse(b []byte) ([]*store.Response, error) {
|
||||
|
||||
var results []*store.Response
|
||||
var result *store.Response
|
||||
|
||||
err := json.Unmarshal(b, &result)
|
||||
|
||||
if err != nil {
|
||||
err = json.Unmarshal(b, &results)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
results = make([]*store.Response, 1)
|
||||
results[0] = result
|
||||
}
|
||||
return results, nil
|
||||
}
|
46
third_party/github.com/coreos/go-etcd/etcd/get_test.go
vendored
Normal file
46
third_party/github.com/coreos/go-etcd/etcd/get_test.go
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
|
||||
c := NewClient()
|
||||
|
||||
c.Set("foo", "bar", 100)
|
||||
|
||||
// wait for commit
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
results, err := c.Get("foo")
|
||||
|
||||
if err != nil || results[0].Key != "/foo" || results[0].Value != "bar" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Get failed with %s %s %v", results[0].Key, results[0].Value, results[0].TTL)
|
||||
}
|
||||
|
||||
results, err = c.Get("goo")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("should not be able to get non-exist key")
|
||||
}
|
||||
|
||||
results, err = c.GetFrom("foo", "0.0.0.0:4001")
|
||||
|
||||
if err != nil || results[0].Key != "/foo" || results[0].Value != "bar" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Get failed with %s %s %v", results[0].Key, results[0].Value, results[0].TTL)
|
||||
}
|
||||
|
||||
results, err = c.GetFrom("foo", "0.0.0.0:4009")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("should not get from port 4009")
|
||||
}
|
||||
}
|
23
third_party/github.com/coreos/go-etcd/etcd/list_test.go
vendored
Normal file
23
third_party/github.com/coreos/go-etcd/etcd/list_test.go
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
c := NewClient()
|
||||
|
||||
c.Set("foo_list/foo", "bar", 100)
|
||||
c.Set("foo_list/fooo", "barbar", 100)
|
||||
c.Set("foo_list/foooo/foo", "barbarbar", 100)
|
||||
// wait for commit
|
||||
time.Sleep(time.Second)
|
||||
|
||||
_, err := c.Get("foo_list")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
90
third_party/github.com/coreos/go-etcd/etcd/set.go
vendored
Normal file
90
third_party/github.com/coreos/go-etcd/etcd/set.go
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/coreos/etcd/store"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (c *Client) Set(key string, value string, ttl uint64) (*store.Response, error) {
|
||||
logger.Debugf("set %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
v := url.Values{}
|
||||
v.Set("value", value)
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
resp, err := c.sendRequest("POST", path.Join("keys", key), v.Encode())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
|
||||
return nil, handleError(b)
|
||||
}
|
||||
|
||||
return convertSetResponse(b)
|
||||
|
||||
}
|
||||
|
||||
// SetTo sets the value of the key to a given machine address.
|
||||
// If the given machine is not available or is not leader it returns an error
|
||||
// Mainly use for testing purpose.
|
||||
func (c *Client) SetTo(key string, value string, ttl uint64, addr string) (*store.Response, error) {
|
||||
v := url.Values{}
|
||||
v.Set("value", value)
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
httpPath := c.createHttpPath(addr, path.Join(version, "keys", key))
|
||||
|
||||
resp, err := c.httpClient.PostForm(httpPath, v)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, handleError(b)
|
||||
}
|
||||
|
||||
return convertSetResponse(b)
|
||||
}
|
||||
|
||||
// Convert byte stream to response.
|
||||
func convertSetResponse(b []byte) (*store.Response, error) {
|
||||
var result store.Response
|
||||
|
||||
err := json.Unmarshal(b, &result)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
42
third_party/github.com/coreos/go-etcd/etcd/set_test.go
vendored
Normal file
42
third_party/github.com/coreos/go-etcd/etcd/set_test.go
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
c := NewClient()
|
||||
|
||||
result, err := c.Set("foo", "bar", 100)
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL != 99 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
result, err = c.Set("foo", "bar", 100)
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL != 99 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
}
|
||||
|
||||
result, err = c.SetTo("toFoo", "bar", 100, "0.0.0.0:4001")
|
||||
|
||||
if err != nil || result.Key != "/toFoo" || result.Value != "bar" || result.TTL != 99 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatalf("SetTo failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
}
|
||||
|
||||
}
|
57
third_party/github.com/coreos/go-etcd/etcd/testAndSet.go
vendored
Normal file
57
third_party/github.com/coreos/go-etcd/etcd/testAndSet.go
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/coreos/etcd/store"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (c *Client) TestAndSet(key string, prevValue string, value string, ttl uint64) (*store.Response, bool, error) {
|
||||
logger.Debugf("set %s, %s[%s], ttl: %d, [%s]", key, value, prevValue, ttl, c.cluster.Leader)
|
||||
v := url.Values{}
|
||||
v.Set("value", value)
|
||||
v.Set("prevValue", prevValue)
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
resp, err := c.sendRequest("POST", path.Join("keys", key), v.Encode())
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, false, handleError(b)
|
||||
}
|
||||
|
||||
var result store.Response
|
||||
|
||||
err = json.Unmarshal(b, &result)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if result.PrevValue == prevValue && result.Value == value {
|
||||
|
||||
return &result, true, nil
|
||||
}
|
||||
|
||||
return &result, false, nil
|
||||
|
||||
}
|
39
third_party/github.com/coreos/go-etcd/etcd/testAndSet_test.go
vendored
Normal file
39
third_party/github.com/coreos/go-etcd/etcd/testAndSet_test.go
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTestAndSet(t *testing.T) {
|
||||
c := NewClient()
|
||||
|
||||
c.Set("foo_testAndSet", "bar", 100)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
results := make(chan bool, 3)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
testAndSet("foo_testAndSet", "bar", "barbar", results, c)
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
result := <-results
|
||||
if result {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
t.Fatalf("test and set fails %v", count)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testAndSet(key string, prevValue string, value string, ch chan bool, c *Client) {
|
||||
_, success, _ := c.TestAndSet(key, prevValue, value, 0)
|
||||
ch <- success
|
||||
}
|
3
third_party/github.com/coreos/go-etcd/etcd/version.go
vendored
Normal file
3
third_party/github.com/coreos/go-etcd/etcd/version.go
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package etcd
|
||||
|
||||
var version = "v1"
|
117
third_party/github.com/coreos/go-etcd/etcd/watch.go
vendored
Normal file
117
third_party/github.com/coreos/go-etcd/etcd/watch.go
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/coreos/etcd/store"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
type respAndErr struct {
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
// Watch any change under the given prefix.
|
||||
// When a sinceIndex is given, watch will try to scan from that index to the last index
|
||||
// and will return any changes under the given prefix during the history
|
||||
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
|
||||
// channel. And after someone receive the channel, it will go on to watch that prefix.
|
||||
// If a stop channel is given, client can close long-term watch using the stop channel
|
||||
|
||||
func (c *Client) Watch(prefix string, sinceIndex uint64, receiver chan *store.Response, stop chan bool) (*store.Response, error) {
|
||||
logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
return c.watchOnce(prefix, sinceIndex, stop)
|
||||
|
||||
} else {
|
||||
for {
|
||||
resp, err := c.watchOnce(prefix, sinceIndex, stop)
|
||||
if resp != nil {
|
||||
sinceIndex = resp.Index + 1
|
||||
receiver <- resp
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// helper func
|
||||
// return when there is change under the given prefix
|
||||
func (c *Client) watchOnce(key string, sinceIndex uint64, stop chan bool) (*store.Response, error) {
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
if sinceIndex == 0 {
|
||||
// Get request if no index is given
|
||||
resp, err = c.sendRequest("GET", path.Join("watch", key), "")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Post
|
||||
v := url.Values{}
|
||||
v.Set("index", fmt.Sprintf("%v", sinceIndex))
|
||||
|
||||
ch := make(chan respAndErr)
|
||||
|
||||
if stop != nil {
|
||||
go func() {
|
||||
resp, err = c.sendRequest("POST", path.Join("watch", key), v.Encode())
|
||||
|
||||
ch <- respAndErr{resp, err}
|
||||
}()
|
||||
|
||||
// select at stop or continue to receive
|
||||
select {
|
||||
|
||||
case res := <-ch:
|
||||
resp, err = res.resp, res.err
|
||||
|
||||
case <-stop:
|
||||
resp, err = nil, errors.New("User stoped watch")
|
||||
}
|
||||
} else {
|
||||
resp, err = c.sendRequest("POST", path.Join("watch", key), v.Encode())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
|
||||
return nil, handleError(b)
|
||||
}
|
||||
|
||||
var result store.Response
|
||||
|
||||
err = json.Unmarshal(b, &result)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
62
third_party/github.com/coreos/go-etcd/etcd/watch_test.go
vendored
Normal file
62
third_party/github.com/coreos/go-etcd/etcd/watch_test.go
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/coreos/etcd/store"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
c := NewClient()
|
||||
|
||||
go setHelper("bar", c)
|
||||
|
||||
result, err := c.Watch("watch_foo", 0, nil, nil)
|
||||
|
||||
if err != nil || result.Key != "/watch_foo/foo" || result.Value != "bar" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Watch failed with %s %s %v %v", result.Key, result.Value, result.TTL, result.Index)
|
||||
}
|
||||
|
||||
result, err = c.Watch("watch_foo", result.Index, nil, nil)
|
||||
|
||||
if err != nil || result.Key != "/watch_foo/foo" || result.Value != "bar" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Watch with Index failed with %s %s %v %v", result.Key, result.Value, result.TTL, result.Index)
|
||||
}
|
||||
|
||||
ch := make(chan *store.Response, 10)
|
||||
stop := make(chan bool, 1)
|
||||
|
||||
go setLoop("bar", c)
|
||||
|
||||
go reciver(ch, stop)
|
||||
|
||||
c.Watch("watch_foo", 0, ch, stop)
|
||||
}
|
||||
|
||||
func setHelper(value string, c *Client) {
|
||||
time.Sleep(time.Second)
|
||||
c.Set("watch_foo/foo", value, 100)
|
||||
}
|
||||
|
||||
func setLoop(value string, c *Client) {
|
||||
time.Sleep(time.Second)
|
||||
for i := 0; i < 10; i++ {
|
||||
newValue := fmt.Sprintf("%s_%v", value, i)
|
||||
c.Set("watch_foo/foo", newValue, 100)
|
||||
time.Sleep(time.Second / 10)
|
||||
}
|
||||
}
|
||||
|
||||
func reciver(c chan *store.Response, stop chan bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
<-c
|
||||
}
|
||||
stop <- true
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user