diff --git a/elasticache/elasticache.go b/elasticache/elasticache.go index 4023e18..6e4ba41 100644 --- a/elasticache/elasticache.go +++ b/elasticache/elasticache.go @@ -13,6 +13,7 @@ import ( "github.com/bradfitz/gomemcache/memcache" "github.com/integralist/go-findroot/find" + "github.com/thompsonlabs/go-elasticache/lister" ) // Node is a single ElastiCache node @@ -29,6 +30,7 @@ type Item memcache.Item // Client embeds the memcache client so we can hide those details away type Client struct { *memcache.Client + clusterNodeLister *lister.ClusterNodesKeyLister } // Set abstracts the memcache client details away, @@ -45,6 +47,10 @@ func (c *Client) Set(item *Item) error { var logger *log.Logger +//new var to hold endpoint environment variable name this instance should use. +//this allows each instance of this class to be associated with their own endpoint. +var endpointEnvironmentVarName string + func init() { logger = log.New(os.Stdout, "go-elasticache: ", log.Ldate|log.Ltime|log.Lshortfile) @@ -67,12 +73,40 @@ func init() { // New returns an instance of the memcache client func New() (*Client, error) { + endpointEnvironmentVarName = "ELASTICACHE_ENDPOINT" urls, err := clusterNodes() if err != nil { return &Client{Client: memcache.New()}, err } - return &Client{Client: memcache.New(urls...)}, nil + return &Client{Client: memcache.New(urls...), + clusterNodeLister: lister.NewClusterNodeKeyLister(urls)}, nil +} + +// NewInstance - returns an instance of the memcache client, this alternative constructor +// allows an endpoint environment variable to be specified specific to this +// instance. Where a value is not provided the default value: ELASTICACHE_ENDPOINT +// will be used. +func NewInstance(endpointEnvVarName string) (*Client, error) { + if len(endpointEnvVarName) < 1 { + endpointEnvironmentVarName = "ELASTICACHE_ENDPOINT" + } else { + endpointEnvironmentVarName = endpointEnvVarName + } + + urls, err := clusterNodes() + if err != nil { + return &Client{Client: memcache.New()}, err + } + + return &Client{Client: memcache.New(urls...), + clusterNodeLister: lister.NewClusterNodeKeyLister(urls)}, nil +} + +//Lists all keys stored accross all nodes in the Cluster. +func (c *Client) ListAllKeys() (*[]string, error) { + + return c.clusterNodeLister.ListAllHostKeys() } func clusterNodes() ([]string, error) { @@ -107,7 +141,7 @@ func clusterNodes() ([]string, error) { func elasticache() (string, error) { var endpoint string - endpoint = os.Getenv("ELASTICACHE_ENDPOINT") + endpoint = os.Getenv(endpointEnvironmentVarName) if len(endpoint) == 0 { logger.Println("ElastiCache endpoint not set") return "", errors.New("ElastiCache endpoint not set") diff --git a/glide.lock b/glide.lock deleted file mode 100644 index 3be1f49..0000000 --- a/glide.lock +++ /dev/null @@ -1,12 +0,0 @@ -hash: 61f0d96c966c7a1f175801e09ae467b4d9d12a4ae0b1a090853395b2e0b62fa3 -updated: 2018-01-22T15:47:11.740223268-08:00 -imports: -- name: github.com/bradfitz/gomemcache - version: 1952afaa557dc08e8e0d89eafab110fb501c1a2b - subpackages: - - memcache -- name: github.com/integralist/go-findroot - version: ac90681525dc30c2163cc606675922b7fdb9c041 - subpackages: - - find -testImports: [] diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index a6d62fd..0000000 --- a/glide.yaml +++ /dev/null @@ -1,4 +0,0 @@ -package: github.com/integralist/go-elasticache -import: -- package: github.com/bradfitz/gomemcache -- package: github.com/integralist/go-findroot diff --git a/go-elasticache b/go-elasticache new file mode 100755 index 0000000..66c108c Binary files /dev/null and b/go-elasticache differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..820d5dd --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/thompsonlabs/go-elasticache + +go 1.16 + +require ( + github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d + github.com/integralist/go-findroot v0.0.0-20160518114804-ac90681525dc +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3e86bb2 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d h1:7IjN4QP3c38xhg6wz8R3YjoU+6S9e7xBc0DAVLLIpHE= +github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/integralist/go-findroot v0.0.0-20160518114804-ac90681525dc h1:4IZpk3M4m6ypx0IlRoEyEyY1gAdicWLMQ0NcG/gBnnA= +github.com/integralist/go-findroot v0.0.0-20160518114804-ac90681525dc/go.mod h1:UlaC6ndby46IJz9m/03cZPKKkR9ykeIVBBDE3UDBdJk= diff --git a/lister/ClusterNodeKeyLister.go b/lister/ClusterNodeKeyLister.go new file mode 100644 index 0000000..239972e --- /dev/null +++ b/lister/ClusterNodeKeyLister.go @@ -0,0 +1,134 @@ +package lister + +import ( + "bufio" + "fmt" + "log" + "net" + "regexp" + "strconv" +) + +/** + Utility component that iterates over all nodes in the memchached + cluster and lists all their respective keys. +*/ +type ClusterNodesKeyLister struct { + clusterNodeUrls []string +} + +//NewClusterNodeKeyLister - Returns a new ClusterNodeKeyLister instance. +func NewClusterNodeKeyLister(nodeUrls []string) *ClusterNodesKeyLister { + + return &ClusterNodesKeyLister{ + + clusterNodeUrls: nodeUrls, + } + +} + +//ListAllHostKeys - Lists all keys associated with all nodes in the cluster. +func (cnkl *ClusterNodesKeyLister) ListAllHostKeys() (*[]string, error) { + + allClusterNodeKeys := make([]string, 0) + + for _, currentNode := range cnkl.clusterNodeUrls { + + currentNodeKeys, err := cnkl.listHostKeys(currentNode) + + if err != nil { + + log.Println("An error occured whilst attempting to fetch keys for node: " + currentNode + " " + err.Error()) + + } else { + + for _, currentNodeCurrentKey := range *currentNodeKeys { + + allClusterNodeKeys = append(allClusterNodeKeys, currentNodeCurrentKey) + } + } + + } + + return &allClusterNodeKeys, nil +} + +//private functions. + +func (cnkl *ClusterNodesKeyLister) getNewConnection(hostAndPortNumber string) (net.Conn, error) { + + conn, err := net.Dial("tcp", hostAndPortNumber) + + if err != nil { + + return nil, err + } + + return conn, nil + +} + +func (cnkl *ClusterNodesKeyLister) dispatchRequestAndReadResponse(connection net.Conn, command string, responseDelimiters []string) []string { + fmt.Fprintf(connection, command) + scanner := bufio.NewScanner(connection) + var result []string + +OUTER: + for scanner.Scan() { + line := scanner.Text() + for _, delimeter := range responseDelimiters { + if line == delimeter { + break OUTER + } + } + result = append(result, line) + // if there is no delimiter specified, then the response is just a single line and we should return after + // reading that first line (e.g. version command) + if len(responseDelimiters) == 0 { + break OUTER + } + } + return result +} + +func (cnkl *ClusterNodesKeyLister) listHostKeys(aHostAddressAndPort string) (*[]string, error) { + keys := make([]string, 0) + conn, err := cnkl.getNewConnection(aHostAddressAndPort) + if err != nil { + + log.Println("An error occured whilst attempting to connect to Memcached cluster node at: " + aHostAddressAndPort + " " + err.Error()) + + return nil, err + } + + //result := client.executer.execute("stats items\r\n", []string{"END"}) + result := cnkl.dispatchRequestAndReadResponse(conn, "stats items\r\n", []string{"END"}) + + // identify all slabs and their number of items by parsing the 'stats items' command + r, _ := regexp.Compile("STAT items:([0-9]*):number ([0-9]*)") + slabCounts := map[int]int{} + for _, stat := range result { + matches := r.FindStringSubmatch(stat) + if len(matches) == 3 { + slabId, _ := strconv.Atoi(matches[1]) + slabItemCount, _ := strconv.Atoi(matches[2]) + slabCounts[slabId] = slabItemCount + } + } + + // For each slab, dump all items and add each key to the `keys` slice + r, _ = regexp.Compile("ITEM (.*?) .*") + for slabId, slabCount := range slabCounts { + command := fmt.Sprintf("stats cachedump %v %v\n", slabId, slabCount) + //commandResult := client.executer.execute(command, []string{"END"}) + commandResult := cnkl.dispatchRequestAndReadResponse(conn, command, []string{"END"}) + for _, item := range commandResult { + matches := r.FindStringSubmatch(item) + keys = append(keys, matches[1]) + } + } + + conn.Close() + + return &keys, nil +} diff --git a/vendor/github.com/bradfitz/gomemcache/.gitignore b/vendor/github.com/bradfitz/gomemcache/.gitignore deleted file mode 100644 index 02c604d..0000000 --- a/vendor/github.com/bradfitz/gomemcache/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -_* -*.out -*~ diff --git a/vendor/github.com/bradfitz/gomemcache/LICENSE b/vendor/github.com/bradfitz/gomemcache/LICENSE deleted file mode 100644 index d645695..0000000 --- a/vendor/github.com/bradfitz/gomemcache/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/bradfitz/gomemcache/README.md b/vendor/github.com/bradfitz/gomemcache/README.md deleted file mode 100644 index f987363..0000000 --- a/vendor/github.com/bradfitz/gomemcache/README.md +++ /dev/null @@ -1,37 +0,0 @@ -## About - -This is a memcache client library for the Go programming language -(http://golang.org/). - -## Installing - -### Using *go get* - - $ go get github.com/bradfitz/gomemcache/memcache - -After this command *gomemcache* is ready to use. Its source will be in: - - $GOPATH/src/github.com/bradfitz/gomemcache/memcache - -## Example - - import ( - "github.com/bradfitz/gomemcache/memcache" - ) - - func main() { - mc := memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212") - mc.Set(&memcache.Item{Key: "foo", Value: []byte("my value")}) - - it, err := mc.Get("foo") - ... - } - -## Full docs, see: - -See https://godoc.org/github.com/bradfitz/gomemcache/memcache - -Or run: - - $ godoc github.com/bradfitz/gomemcache/memcache - diff --git a/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go b/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go deleted file mode 100644 index b98a765..0000000 --- a/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go +++ /dev/null @@ -1,684 +0,0 @@ -/* -Copyright 2011 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package memcache provides a client for the memcached cache server. -package memcache - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - - "strconv" - "strings" - "sync" - "time" -) - -// Similar to: -// http://code.google.com/appengine/docs/go/memcache/reference.html - -var ( - // ErrCacheMiss means that a Get failed because the item wasn't present. - ErrCacheMiss = errors.New("memcache: cache miss") - - // ErrCASConflict means that a CompareAndSwap call failed due to the - // cached value being modified between the Get and the CompareAndSwap. - // If the cached value was simply evicted rather than replaced, - // ErrNotStored will be returned instead. - ErrCASConflict = errors.New("memcache: compare-and-swap conflict") - - // ErrNotStored means that a conditional write operation (i.e. Add or - // CompareAndSwap) failed because the condition was not satisfied. - ErrNotStored = errors.New("memcache: item not stored") - - // ErrServer means that a server error occurred. - ErrServerError = errors.New("memcache: server error") - - // ErrNoStats means that no statistics were available. - ErrNoStats = errors.New("memcache: no statistics available") - - // ErrMalformedKey is returned when an invalid key is used. - // Keys must be at maximum 250 bytes long and not - // contain whitespace or control characters. - ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters") - - // ErrNoServers is returned when no servers are configured or available. - ErrNoServers = errors.New("memcache: no servers configured or available") -) - -const ( - // DefaultTimeout is the default socket read/write timeout. - DefaultTimeout = 100 * time.Millisecond - - // DefaultMaxIdleConns is the default maximum number of idle connections - // kept for any single address. - DefaultMaxIdleConns = 2 -) - -const buffered = 8 // arbitrary buffered channel size, for readability - -// resumableError returns true if err is only a protocol-level cache error. -// This is used to determine whether or not a server connection should -// be re-used or not. If an error occurs, by default we don't reuse the -// connection, unless it was just a cache error. -func resumableError(err error) bool { - switch err { - case ErrCacheMiss, ErrCASConflict, ErrNotStored, ErrMalformedKey: - return true - } - return false -} - -func legalKey(key string) bool { - if len(key) > 250 { - return false - } - for i := 0; i < len(key); i++ { - if key[i] <= ' ' || key[i] == 0x7f { - return false - } - } - return true -} - -var ( - crlf = []byte("\r\n") - space = []byte(" ") - resultOK = []byte("OK\r\n") - resultStored = []byte("STORED\r\n") - resultNotStored = []byte("NOT_STORED\r\n") - resultExists = []byte("EXISTS\r\n") - resultNotFound = []byte("NOT_FOUND\r\n") - resultDeleted = []byte("DELETED\r\n") - resultEnd = []byte("END\r\n") - resultOk = []byte("OK\r\n") - resultTouched = []byte("TOUCHED\r\n") - - resultClientErrorPrefix = []byte("CLIENT_ERROR ") -) - -// New returns a memcache client using the provided server(s) -// with equal weight. If a server is listed multiple times, -// it gets a proportional amount of weight. -func New(server ...string) *Client { - ss := new(ServerList) - ss.SetServers(server...) - return NewFromSelector(ss) -} - -// NewFromSelector returns a new Client using the provided ServerSelector. -func NewFromSelector(ss ServerSelector) *Client { - return &Client{selector: ss} -} - -// Client is a memcache client. -// It is safe for unlocked use by multiple concurrent goroutines. -type Client struct { - // Timeout specifies the socket read/write timeout. - // If zero, DefaultTimeout is used. - Timeout time.Duration - - // MaxIdleConns specifies the maximum number of idle connections that will - // be maintained per address. If less than one, DefaultMaxIdleConns will be - // used. - // - // Consider your expected traffic rates and latency carefully. This should - // be set to a number higher than your peak parallel requests. - MaxIdleConns int - - selector ServerSelector - - lk sync.Mutex - freeconn map[string][]*conn -} - -// Item is an item to be got or stored in a memcached server. -type Item struct { - // Key is the Item's key (250 bytes maximum). - Key string - - // Value is the Item's value. - Value []byte - - // Flags are server-opaque flags whose semantics are entirely - // up to the app. - Flags uint32 - - // Expiration is the cache expiration time, in seconds: either a relative - // time from now (up to 1 month), or an absolute Unix epoch time. - // Zero means the Item has no expiration time. - Expiration int32 - - // Compare and swap ID. - casid uint64 -} - -// conn is a connection to a server. -type conn struct { - nc net.Conn - rw *bufio.ReadWriter - addr net.Addr - c *Client -} - -// release returns this connection back to the client's free pool -func (cn *conn) release() { - cn.c.putFreeConn(cn.addr, cn) -} - -func (cn *conn) extendDeadline() { - cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout())) -} - -// condRelease releases this connection if the error pointed to by err -// is nil (not an error) or is only a protocol level error (e.g. a -// cache miss). The purpose is to not recycle TCP connections that -// are bad. -func (cn *conn) condRelease(err *error) { - if *err == nil || resumableError(*err) { - cn.release() - } else { - cn.nc.Close() - } -} - -func (c *Client) putFreeConn(addr net.Addr, cn *conn) { - c.lk.Lock() - defer c.lk.Unlock() - if c.freeconn == nil { - c.freeconn = make(map[string][]*conn) - } - freelist := c.freeconn[addr.String()] - if len(freelist) >= c.maxIdleConns() { - cn.nc.Close() - return - } - c.freeconn[addr.String()] = append(freelist, cn) -} - -func (c *Client) getFreeConn(addr net.Addr) (cn *conn, ok bool) { - c.lk.Lock() - defer c.lk.Unlock() - if c.freeconn == nil { - return nil, false - } - freelist, ok := c.freeconn[addr.String()] - if !ok || len(freelist) == 0 { - return nil, false - } - cn = freelist[len(freelist)-1] - c.freeconn[addr.String()] = freelist[:len(freelist)-1] - return cn, true -} - -func (c *Client) netTimeout() time.Duration { - if c.Timeout != 0 { - return c.Timeout - } - return DefaultTimeout -} - -func (c *Client) maxIdleConns() int { - if c.MaxIdleConns > 0 { - return c.MaxIdleConns - } - return DefaultMaxIdleConns -} - -// ConnectTimeoutError is the error type used when it takes -// too long to connect to the desired host. This level of -// detail can generally be ignored. -type ConnectTimeoutError struct { - Addr net.Addr -} - -func (cte *ConnectTimeoutError) Error() string { - return "memcache: connect timeout to " + cte.Addr.String() -} - -func (c *Client) dial(addr net.Addr) (net.Conn, error) { - type connError struct { - cn net.Conn - err error - } - - nc, err := net.DialTimeout(addr.Network(), addr.String(), c.netTimeout()) - if err == nil { - return nc, nil - } - - if ne, ok := err.(net.Error); ok && ne.Timeout() { - return nil, &ConnectTimeoutError{addr} - } - - return nil, err -} - -func (c *Client) getConn(addr net.Addr) (*conn, error) { - cn, ok := c.getFreeConn(addr) - if ok { - cn.extendDeadline() - return cn, nil - } - nc, err := c.dial(addr) - if err != nil { - return nil, err - } - cn = &conn{ - nc: nc, - addr: addr, - rw: bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc)), - c: c, - } - cn.extendDeadline() - return cn, nil -} - -func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) error) error { - addr, err := c.selector.PickServer(item.Key) - if err != nil { - return err - } - cn, err := c.getConn(addr) - if err != nil { - return err - } - defer cn.condRelease(&err) - if err = fn(c, cn.rw, item); err != nil { - return err - } - return nil -} - -func (c *Client) FlushAll() error { - return c.selector.Each(c.flushAllFromAddr) -} - -// Get gets the item for the given key. ErrCacheMiss is returned for a -// memcache cache miss. The key must be at most 250 bytes in length. -func (c *Client) Get(key string) (item *Item, err error) { - err = c.withKeyAddr(key, func(addr net.Addr) error { - return c.getFromAddr(addr, []string{key}, func(it *Item) { item = it }) - }) - if err == nil && item == nil { - err = ErrCacheMiss - } - return -} - -// Touch updates the expiry for the given key. The seconds parameter is either -// a Unix timestamp or, if seconds is less than 1 month, the number of seconds -// into the future at which time the item will expire. ErrCacheMiss is returned if the -// key is not in the cache. The key must be at most 250 bytes in length. -func (c *Client) Touch(key string, seconds int32) (err error) { - return c.withKeyAddr(key, func(addr net.Addr) error { - return c.touchFromAddr(addr, []string{key}, seconds) - }) -} - -func (c *Client) withKeyAddr(key string, fn func(net.Addr) error) (err error) { - if !legalKey(key) { - return ErrMalformedKey - } - addr, err := c.selector.PickServer(key) - if err != nil { - return err - } - return fn(addr) -} - -func (c *Client) withAddrRw(addr net.Addr, fn func(*bufio.ReadWriter) error) (err error) { - cn, err := c.getConn(addr) - if err != nil { - return err - } - defer cn.condRelease(&err) - return fn(cn.rw) -} - -func (c *Client) withKeyRw(key string, fn func(*bufio.ReadWriter) error) error { - return c.withKeyAddr(key, func(addr net.Addr) error { - return c.withAddrRw(addr, fn) - }) -} - -func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) error { - return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { - if _, err := fmt.Fprintf(rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil { - return err - } - if err := rw.Flush(); err != nil { - return err - } - if err := parseGetResponse(rw.Reader, cb); err != nil { - return err - } - return nil - }) -} - -// flushAllFromAddr send the flush_all command to the given addr -func (c *Client) flushAllFromAddr(addr net.Addr) error { - return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { - if _, err := fmt.Fprintf(rw, "flush_all\r\n"); err != nil { - return err - } - if err := rw.Flush(); err != nil { - return err - } - line, err := rw.ReadSlice('\n') - if err != nil { - return err - } - switch { - case bytes.Equal(line, resultOk): - break - default: - return fmt.Errorf("memcache: unexpected response line from flush_all: %q", string(line)) - } - return nil - }) -} - -func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) error { - return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { - for _, key := range keys { - if _, err := fmt.Fprintf(rw, "touch %s %d\r\n", key, expiration); err != nil { - return err - } - if err := rw.Flush(); err != nil { - return err - } - line, err := rw.ReadSlice('\n') - if err != nil { - return err - } - switch { - case bytes.Equal(line, resultTouched): - break - case bytes.Equal(line, resultNotFound): - return ErrCacheMiss - default: - return fmt.Errorf("memcache: unexpected response line from touch: %q", string(line)) - } - } - return nil - }) -} - -// GetMulti is a batch version of Get. The returned map from keys to -// items may have fewer elements than the input slice, due to memcache -// cache misses. Each key must be at most 250 bytes in length. -// If no error is returned, the returned map will also be non-nil. -func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { - var lk sync.Mutex - m := make(map[string]*Item) - addItemToMap := func(it *Item) { - lk.Lock() - defer lk.Unlock() - m[it.Key] = it - } - - keyMap := make(map[net.Addr][]string) - for _, key := range keys { - if !legalKey(key) { - return nil, ErrMalformedKey - } - addr, err := c.selector.PickServer(key) - if err != nil { - return nil, err - } - keyMap[addr] = append(keyMap[addr], key) - } - - ch := make(chan error, buffered) - for addr, keys := range keyMap { - go func(addr net.Addr, keys []string) { - ch <- c.getFromAddr(addr, keys, addItemToMap) - }(addr, keys) - } - - var err error - for _ = range keyMap { - if ge := <-ch; ge != nil { - err = ge - } - } - return m, err -} - -// parseGetResponse reads a GET response from r and calls cb for each -// read and allocated Item -func parseGetResponse(r *bufio.Reader, cb func(*Item)) error { - for { - line, err := r.ReadSlice('\n') - if err != nil { - return err - } - if bytes.Equal(line, resultEnd) { - return nil - } - it := new(Item) - size, err := scanGetResponseLine(line, it) - if err != nil { - return err - } - it.Value, err = ioutil.ReadAll(io.LimitReader(r, int64(size)+2)) - if err != nil { - return err - } - if !bytes.HasSuffix(it.Value, crlf) { - return fmt.Errorf("memcache: corrupt get result read") - } - it.Value = it.Value[:size] - cb(it) - } -} - -// scanGetResponseLine populates it and returns the declared size of the item. -// It does not read the bytes of the item. -func scanGetResponseLine(line []byte, it *Item) (size int, err error) { - pattern := "VALUE %s %d %d %d\r\n" - dest := []interface{}{&it.Key, &it.Flags, &size, &it.casid} - if bytes.Count(line, space) == 3 { - pattern = "VALUE %s %d %d\r\n" - dest = dest[:3] - } - n, err := fmt.Sscanf(string(line), pattern, dest...) - if err != nil || n != len(dest) { - return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line) - } - return size, nil -} - -// Set writes the given item, unconditionally. -func (c *Client) Set(item *Item) error { - return c.onItem(item, (*Client).set) -} - -func (c *Client) set(rw *bufio.ReadWriter, item *Item) error { - return c.populateOne(rw, "set", item) -} - -// Add writes the given item, if no value already exists for its -// key. ErrNotStored is returned if that condition is not met. -func (c *Client) Add(item *Item) error { - return c.onItem(item, (*Client).add) -} - -func (c *Client) add(rw *bufio.ReadWriter, item *Item) error { - return c.populateOne(rw, "add", item) -} - -// Replace writes the given item, but only if the server *does* -// already hold data for this key -func (c *Client) Replace(item *Item) error { - return c.onItem(item, (*Client).replace) -} - -func (c *Client) replace(rw *bufio.ReadWriter, item *Item) error { - return c.populateOne(rw, "replace", item) -} - -// CompareAndSwap writes the given item that was previously returned -// by Get, if the value was neither modified or evicted between the -// Get and the CompareAndSwap calls. The item's Key should not change -// between calls but all other item fields may differ. ErrCASConflict -// is returned if the value was modified in between the -// calls. ErrNotStored is returned if the value was evicted in between -// the calls. -func (c *Client) CompareAndSwap(item *Item) error { - return c.onItem(item, (*Client).cas) -} - -func (c *Client) cas(rw *bufio.ReadWriter, item *Item) error { - return c.populateOne(rw, "cas", item) -} - -func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) error { - if !legalKey(item.Key) { - return ErrMalformedKey - } - var err error - if verb == "cas" { - _, err = fmt.Fprintf(rw, "%s %s %d %d %d %d\r\n", - verb, item.Key, item.Flags, item.Expiration, len(item.Value), item.casid) - } else { - _, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n", - verb, item.Key, item.Flags, item.Expiration, len(item.Value)) - } - if err != nil { - return err - } - if _, err = rw.Write(item.Value); err != nil { - return err - } - if _, err := rw.Write(crlf); err != nil { - return err - } - if err := rw.Flush(); err != nil { - return err - } - line, err := rw.ReadSlice('\n') - if err != nil { - return err - } - switch { - case bytes.Equal(line, resultStored): - return nil - case bytes.Equal(line, resultNotStored): - return ErrNotStored - case bytes.Equal(line, resultExists): - return ErrCASConflict - case bytes.Equal(line, resultNotFound): - return ErrCacheMiss - } - return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line)) -} - -func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, error) { - _, err := fmt.Fprintf(rw, format, args...) - if err != nil { - return nil, err - } - if err := rw.Flush(); err != nil { - return nil, err - } - line, err := rw.ReadSlice('\n') - return line, err -} - -func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) error { - line, err := writeReadLine(rw, format, args...) - if err != nil { - return err - } - switch { - case bytes.Equal(line, resultOK): - return nil - case bytes.Equal(line, expect): - return nil - case bytes.Equal(line, resultNotStored): - return ErrNotStored - case bytes.Equal(line, resultExists): - return ErrCASConflict - case bytes.Equal(line, resultNotFound): - return ErrCacheMiss - } - return fmt.Errorf("memcache: unexpected response line: %q", string(line)) -} - -// Delete deletes the item with the provided key. The error ErrCacheMiss is -// returned if the item didn't already exist in the cache. -func (c *Client) Delete(key string) error { - return c.withKeyRw(key, func(rw *bufio.ReadWriter) error { - return writeExpectf(rw, resultDeleted, "delete %s\r\n", key) - }) -} - -// DeleteAll deletes all items in the cache. -func (c *Client) DeleteAll() error { - return c.withKeyRw("", func(rw *bufio.ReadWriter) error { - return writeExpectf(rw, resultDeleted, "flush_all\r\n") - }) -} - -// Increment atomically increments key by delta. The return value is -// the new value after being incremented or an error. If the value -// didn't exist in memcached the error is ErrCacheMiss. The value in -// memcached must be an decimal number, or an error will be returned. -// On 64-bit overflow, the new value wraps around. -func (c *Client) Increment(key string, delta uint64) (newValue uint64, err error) { - return c.incrDecr("incr", key, delta) -} - -// Decrement atomically decrements key by delta. The return value is -// the new value after being decremented or an error. If the value -// didn't exist in memcached the error is ErrCacheMiss. The value in -// memcached must be an decimal number, or an error will be returned. -// On underflow, the new value is capped at zero and does not wrap -// around. -func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err error) { - return c.incrDecr("decr", key, delta) -} - -func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) { - var val uint64 - err := c.withKeyRw(key, func(rw *bufio.ReadWriter) error { - line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta) - if err != nil { - return err - } - switch { - case bytes.Equal(line, resultNotFound): - return ErrCacheMiss - case bytes.HasPrefix(line, resultClientErrorPrefix): - errMsg := line[len(resultClientErrorPrefix) : len(line)-2] - return errors.New("memcache: client error: " + string(errMsg)) - } - val, err = strconv.ParseUint(string(line[:len(line)-2]), 10, 64) - if err != nil { - return err - } - return nil - }) - return val, err -} diff --git a/vendor/github.com/bradfitz/gomemcache/memcache/memcache_test.go b/vendor/github.com/bradfitz/gomemcache/memcache/memcache_test.go deleted file mode 100644 index 4b52a91..0000000 --- a/vendor/github.com/bradfitz/gomemcache/memcache/memcache_test.go +++ /dev/null @@ -1,289 +0,0 @@ -/* -Copyright 2011 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package memcache provides a client for the memcached cache server. -package memcache - -import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "net" - "os" - "os/exec" - "strings" - "testing" - "time" -) - -const testServer = "localhost:11211" - -func setup(t *testing.T) bool { - c, err := net.Dial("tcp", testServer) - if err != nil { - t.Skipf("skipping test; no server running at %s", testServer) - } - c.Write([]byte("flush_all\r\n")) - c.Close() - return true -} - -func TestLocalhost(t *testing.T) { - if !setup(t) { - return - } - testWithClient(t, New(testServer)) -} - -// Run the memcached binary as a child process and connect to its unix socket. -func TestUnixSocket(t *testing.T) { - sock := fmt.Sprintf("/tmp/test-gomemcache-%d.sock", os.Getpid()) - cmd := exec.Command("memcached", "-s", sock) - if err := cmd.Start(); err != nil { - t.Skipf("skipping test; couldn't find memcached") - return - } - defer cmd.Wait() - defer cmd.Process.Kill() - - // Wait a bit for the socket to appear. - for i := 0; i < 10; i++ { - if _, err := os.Stat(sock); err == nil { - break - } - time.Sleep(time.Duration(25*i) * time.Millisecond) - } - - testWithClient(t, New(sock)) -} - -func mustSetF(t *testing.T, c *Client) func(*Item) { - return func(it *Item) { - if err := c.Set(it); err != nil { - t.Fatalf("failed to Set %#v: %v", *it, err) - } - } -} - -func testWithClient(t *testing.T, c *Client) { - checkErr := func(err error, format string, args ...interface{}) { - if err != nil { - t.Fatalf(format, args...) - } - } - mustSet := mustSetF(t, c) - - // Set - foo := &Item{Key: "foo", Value: []byte("fooval"), Flags: 123} - err := c.Set(foo) - checkErr(err, "first set(foo): %v", err) - err = c.Set(foo) - checkErr(err, "second set(foo): %v", err) - - // Get - it, err := c.Get("foo") - checkErr(err, "get(foo): %v", err) - if it.Key != "foo" { - t.Errorf("get(foo) Key = %q, want foo", it.Key) - } - if string(it.Value) != "fooval" { - t.Errorf("get(foo) Value = %q, want fooval", string(it.Value)) - } - if it.Flags != 123 { - t.Errorf("get(foo) Flags = %v, want 123", it.Flags) - } - - // Get and set a unicode key - quxKey := "Hello_世界" - qux := &Item{Key: quxKey, Value: []byte("hello world")} - err = c.Set(qux) - checkErr(err, "first set(Hello_世界): %v", err) - it, err = c.Get(quxKey) - checkErr(err, "get(Hello_世界): %v", err) - if it.Key != quxKey { - t.Errorf("get(Hello_世界) Key = %q, want Hello_世界", it.Key) - } - if string(it.Value) != "hello world" { - t.Errorf("get(Hello_世界) Value = %q, want hello world", string(it.Value)) - } - - // Set malformed keys - malFormed := &Item{Key: "foo bar", Value: []byte("foobarval")} - err = c.Set(malFormed) - if err != ErrMalformedKey { - t.Errorf("set(foo bar) should return ErrMalformedKey instead of %v", err) - } - malFormed = &Item{Key: "foo" + string(0x7f), Value: []byte("foobarval")} - err = c.Set(malFormed) - if err != ErrMalformedKey { - t.Errorf("set(foo<0x7f>) should return ErrMalformedKey instead of %v", err) - } - - // Add - bar := &Item{Key: "bar", Value: []byte("barval")} - err = c.Add(bar) - checkErr(err, "first add(foo): %v", err) - if err := c.Add(bar); err != ErrNotStored { - t.Fatalf("second add(foo) want ErrNotStored, got %v", err) - } - - // Replace - baz := &Item{Key: "baz", Value: []byte("bazvalue")} - if err := c.Replace(baz); err != ErrNotStored { - t.Fatalf("expected replace(baz) to return ErrNotStored, got %v", err) - } - err = c.Replace(bar) - checkErr(err, "replaced(foo): %v", err) - - // GetMulti - m, err := c.GetMulti([]string{"foo", "bar"}) - checkErr(err, "GetMulti: %v", err) - if g, e := len(m), 2; g != e { - t.Errorf("GetMulti: got len(map) = %d, want = %d", g, e) - } - if _, ok := m["foo"]; !ok { - t.Fatalf("GetMulti: didn't get key 'foo'") - } - if _, ok := m["bar"]; !ok { - t.Fatalf("GetMulti: didn't get key 'bar'") - } - if g, e := string(m["foo"].Value), "fooval"; g != e { - t.Errorf("GetMulti: foo: got %q, want %q", g, e) - } - if g, e := string(m["bar"].Value), "barval"; g != e { - t.Errorf("GetMulti: bar: got %q, want %q", g, e) - } - - // Delete - err = c.Delete("foo") - checkErr(err, "Delete: %v", err) - it, err = c.Get("foo") - if err != ErrCacheMiss { - t.Errorf("post-Delete want ErrCacheMiss, got %v", err) - } - - // Incr/Decr - mustSet(&Item{Key: "num", Value: []byte("42")}) - n, err := c.Increment("num", 8) - checkErr(err, "Increment num + 8: %v", err) - if n != 50 { - t.Fatalf("Increment num + 8: want=50, got=%d", n) - } - n, err = c.Decrement("num", 49) - checkErr(err, "Decrement: %v", err) - if n != 1 { - t.Fatalf("Decrement 49: want=1, got=%d", n) - } - err = c.Delete("num") - checkErr(err, "delete num: %v", err) - n, err = c.Increment("num", 1) - if err != ErrCacheMiss { - t.Fatalf("increment post-delete: want ErrCacheMiss, got %v", err) - } - mustSet(&Item{Key: "num", Value: []byte("not-numeric")}) - n, err = c.Increment("num", 1) - if err == nil || !strings.Contains(err.Error(), "client error") { - t.Fatalf("increment non-number: want client error, got %v", err) - } - testTouchWithClient(t, c) - - // Test Delete All - err = c.DeleteAll() - checkErr(err, "DeleteAll: %v", err) - it, err = c.Get("bar") - if err != ErrCacheMiss { - t.Errorf("post-DeleteAll want ErrCacheMiss, got %v", err) - } - -} - -func testTouchWithClient(t *testing.T, c *Client) { - if testing.Short() { - t.Log("Skipping testing memcache Touch with testing in Short mode") - return - } - - mustSet := mustSetF(t, c) - - const secondsToExpiry = int32(2) - - // We will set foo and bar to expire in 2 seconds, then we'll keep touching - // foo every second - // After 3 seconds, we expect foo to be available, and bar to be expired - foo := &Item{Key: "foo", Value: []byte("fooval"), Expiration: secondsToExpiry} - bar := &Item{Key: "bar", Value: []byte("barval"), Expiration: secondsToExpiry} - - setTime := time.Now() - mustSet(foo) - mustSet(bar) - - for s := 0; s < 3; s++ { - time.Sleep(time.Duration(1 * time.Second)) - err := c.Touch(foo.Key, secondsToExpiry) - if nil != err { - t.Errorf("error touching foo: %v", err.Error()) - } - } - - _, err := c.Get("foo") - if err != nil { - if err == ErrCacheMiss { - t.Fatalf("touching failed to keep item foo alive") - } else { - t.Fatalf("unexpected error retrieving foo after touching: %v", err.Error()) - } - } - - _, err = c.Get("bar") - if nil == err { - t.Fatalf("item bar did not expire within %v seconds", time.Now().Sub(setTime).Seconds()) - } else { - if err != ErrCacheMiss { - t.Fatalf("unexpected error retrieving bar: %v", err.Error()) - } - } -} - -func BenchmarkOnItem(b *testing.B) { - fakeServer, err := net.Listen("tcp", "localhost:0") - if err != nil { - b.Fatal("Could not open fake server: ", err) - } - defer fakeServer.Close() - go func() { - for { - if c, err := fakeServer.Accept(); err == nil { - go func() { io.Copy(ioutil.Discard, c) }() - } else { - return - } - } - }() - - addr := fakeServer.Addr() - c := New(addr.String()) - if _, err := c.getConn(addr); err != nil { - b.Fatal("failed to initialize connection to fake server") - } - - item := Item{Key: "foo"} - dummyFn := func(_ *Client, _ *bufio.ReadWriter, _ *Item) error { return nil } - b.ResetTimer() - for i := 0; i < b.N; i++ { - c.onItem(&item, dummyFn) - } -} diff --git a/vendor/github.com/bradfitz/gomemcache/memcache/selector.go b/vendor/github.com/bradfitz/gomemcache/memcache/selector.go deleted file mode 100644 index 89ad81e..0000000 --- a/vendor/github.com/bradfitz/gomemcache/memcache/selector.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -Copyright 2011 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package memcache - -import ( - "hash/crc32" - "net" - "strings" - "sync" -) - -// ServerSelector is the interface that selects a memcache server -// as a function of the item's key. -// -// All ServerSelector implementations must be safe for concurrent use -// by multiple goroutines. -type ServerSelector interface { - // PickServer returns the server address that a given item - // should be shared onto. - PickServer(key string) (net.Addr, error) - Each(func(net.Addr) error) error -} - -// ServerList is a simple ServerSelector. Its zero value is usable. -type ServerList struct { - mu sync.RWMutex - addrs []net.Addr -} - -// staticAddr caches the Network() and String() values from any net.Addr. -type staticAddr struct { - ntw, str string -} - -func newStaticAddr(a net.Addr) net.Addr { - return &staticAddr{ - ntw: a.Network(), - str: a.String(), - } -} - -func (s *staticAddr) Network() string { return s.ntw } -func (s *staticAddr) String() string { return s.str } - -// SetServers changes a ServerList's set of servers at runtime and is -// safe for concurrent use by multiple goroutines. -// -// Each server is given equal weight. A server is given more weight -// if it's listed multiple times. -// -// SetServers returns an error if any of the server names fail to -// resolve. No attempt is made to connect to the server. If any error -// is returned, no changes are made to the ServerList. -func (ss *ServerList) SetServers(servers ...string) error { - naddr := make([]net.Addr, len(servers)) - for i, server := range servers { - if strings.Contains(server, "/") { - addr, err := net.ResolveUnixAddr("unix", server) - if err != nil { - return err - } - naddr[i] = newStaticAddr(addr) - } else { - tcpaddr, err := net.ResolveTCPAddr("tcp", server) - if err != nil { - return err - } - naddr[i] = newStaticAddr(tcpaddr) - } - } - - ss.mu.Lock() - defer ss.mu.Unlock() - ss.addrs = naddr - return nil -} - -// Each iterates over each server calling the given function -func (ss *ServerList) Each(f func(net.Addr) error) error { - ss.mu.RLock() - defer ss.mu.RUnlock() - for _, a := range ss.addrs { - if err := f(a); nil != err { - return err - } - } - return nil -} - -// keyBufPool returns []byte buffers for use by PickServer's call to -// crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the -// copies, which at least are bounded in size and small) -var keyBufPool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 256) - return &b - }, -} - -func (ss *ServerList) PickServer(key string) (net.Addr, error) { - ss.mu.RLock() - defer ss.mu.RUnlock() - if len(ss.addrs) == 0 { - return nil, ErrNoServers - } - if len(ss.addrs) == 1 { - return ss.addrs[0], nil - } - bufp := keyBufPool.Get().(*[]byte) - n := copy(*bufp, key) - cs := crc32.ChecksumIEEE((*bufp)[:n]) - keyBufPool.Put(bufp) - - return ss.addrs[cs%uint32(len(ss.addrs))], nil -} diff --git a/vendor/github.com/bradfitz/gomemcache/memcache/selector_test.go b/vendor/github.com/bradfitz/gomemcache/memcache/selector_test.go deleted file mode 100644 index 65a2c4d..0000000 --- a/vendor/github.com/bradfitz/gomemcache/memcache/selector_test.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2014 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package memcache - -import "testing" - -func BenchmarkPickServer(b *testing.B) { - // at least two to avoid 0 and 1 special cases: - benchPickServer(b, "127.0.0.1:1234", "127.0.0.1:1235") -} - -func BenchmarkPickServer_Single(b *testing.B) { - benchPickServer(b, "127.0.0.1:1234") -} - -func benchPickServer(b *testing.B, servers ...string) { - b.ReportAllocs() - var ss ServerList - ss.SetServers(servers...) - for i := 0; i < b.N; i++ { - if _, err := ss.PickServer("some key"); err != nil { - b.Fatal(err) - } - } -} diff --git a/vendor/github.com/integralist/go-findroot/LICENSE b/vendor/github.com/integralist/go-findroot/LICENSE deleted file mode 100644 index c7641fd..0000000 --- a/vendor/github.com/integralist/go-findroot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Mark McDonnell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/integralist/go-findroot/README.md b/vendor/github.com/integralist/go-findroot/README.md deleted file mode 100644 index 5343bef..0000000 --- a/vendor/github.com/integralist/go-findroot/README.md +++ /dev/null @@ -1,44 +0,0 @@ -

go-findroot

- -

- -

- -

- Locate the root directory of a project using Git via the command line -

- -## Example - -```go -package main - -import ( - "fmt" - "log" - - "github.com/integralist/go-findroot/find" -) - -func main() { - root, err := find.Repo() - if err != nil { - log.Fatalf("Error: %s", err.Error()) - } - - fmt.Printf("%+v", root) - // {Name:go-findroot Path:/Users/M/Projects/golang/src/github.com/integralist/go-findroot} -} -``` - -## Tests - -```go -go test -v ./... -``` - -## Licence - -[The MIT License (MIT)](http://opensource.org/licenses/MIT) - -Copyright (c) 2016 [Mark McDonnell](http://twitter.com/integralist) diff --git a/vendor/github.com/integralist/go-findroot/find/find.go b/vendor/github.com/integralist/go-findroot/find/find.go deleted file mode 100644 index b87bc0a..0000000 --- a/vendor/github.com/integralist/go-findroot/find/find.go +++ /dev/null @@ -1,41 +0,0 @@ -package find - -import ( - "os/exec" - "strings" -) - -// Stat is exported out of golang convention, rather than necessity -type Stat struct { - Name string - Path string -} - -// Repo uses git via the console to locate the top level directory -func Repo() (Stat, error) { - path, err := rootPath() - if err != nil { - return Stat{ - "Unknown", - "./", - }, err - } - - gitRepo, err := exec.Command("basename", path).Output() - if err != nil { - return Stat{}, err - } - - return Stat{ - strings.TrimSpace(string(gitRepo)), - path, - }, nil -} - -func rootPath() (string, error) { - path, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() - if err != nil { - return "", err - } - return strings.TrimSpace(string(path)), nil -} diff --git a/vendor/github.com/integralist/go-findroot/find/find_test.go b/vendor/github.com/integralist/go-findroot/find/find_test.go deleted file mode 100644 index d62f315..0000000 --- a/vendor/github.com/integralist/go-findroot/find/find_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package find - -import ( - "log" - "testing" -) - -func TestRootIsFound(t *testing.T) { - response, err := Repo() - if err != nil { - log.Fatalf("Error: %s", err.Error()) - } - - expectation := "go-findroot" - - if response.Name != expectation { - t.Errorf("The response '%s' didn't match the expectation '%s'", response.Name, expectation) - } -}