├── .gitmodules ├── make.cmd ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── CONTRIBUTING.md ├── .gitattributes ├── .gitignore ├── net.go ├── connection_manager_test.go ├── rpb ├── riak_dt │ └── riak_dt_misc.go ├── riak │ └── riak_misc.go ├── riak_kv │ └── riak_kv_misc.go └── riak_search │ └── riak_search.pb.go ├── rpb.go ├── object_test.go ├── benchmark_test.go ├── locatable.go ├── .travis.yml ├── NOTICE ├── client_test.go ├── consts.go ├── error_test.go ├── command_i_test.go ├── connection_manager_i_test.go ├── command_test.go ├── client_i_test.go ├── async.go ├── logging_test.go ├── doc.go ├── Makefile ├── states.go ├── node_manager.go ├── queue_test.go ├── RELNOTES.md ├── logging.go ├── gh_issues_i_test.go ├── error.go ├── queue.go ├── states_test.go ├── messages.go ├── connection_test.go ├── examples ├── dev │ └── using │ │ ├── updates │ │ └── main.go │ │ ├── data-types │ │ └── hyperloglog.go │ │ ├── conflict-resolution │ │ └── main.go │ │ └── search │ │ └── main.go ├── cm-client │ └── main.go └── gh-47 │ └── main.go ├── connection_i_test.go ├── security_test.go ├── client.go ├── README.md ├── command.go ├── misc_commands_i_test.go ├── make.ps1 ├── object.go ├── cluster_test.go ├── node_test.go ├── yz_commands_i_test.go ├── globals_i_test.go ├── connection.go ├── node_i_test.go └── node.go /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "riak_pb"] 2 | path = riak_pb 3 | url = git://github.com/basho/riak_pb.git 4 | [submodule "tools"] 5 | path = tools 6 | url = git://github.com/basho/riak-client-tools.git 7 | -------------------------------------------------------------------------------- /make.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0\make.ps1" %* 4 | exit /b %ERRORLEVEL% 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please ensure the following is present in your Pull Request: 2 | 3 | - [ ] Unit tests for your change 4 | - [ ] Integration tests (if applicable) 5 | 6 | Pull Requests that are small and limited in scope are most welcome. 7 | 8 | Thank you! 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | Makefile text eol=lf 4 | .travis.yml text eol=lf 5 | .sh text eol=lf 6 | .pl text eol=lf 7 | 8 | *.jpg binary 9 | *.png binary 10 | *.gif binary 11 | *.ico binary 12 | *.bmp binary 13 | *.mdf binary 14 | *.ldf binary 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please ensure the following information is supplied for new issues: 2 | 3 | - [ ] Riak Go Client version 4 | - [ ] Golang 5 | - [ ] Riak version 6 | - [ ] Operating System / Distribution & Version 7 | - [ ] Riak `error.log` file, if applicable 8 | - [ ] What commands were being executed during the error 9 | - [ ] What you *expected* to have happen 10 | 11 | Thank you! 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .vscode/ 10 | .idea/ 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | *.bin 28 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "net" 19 | ) 20 | 21 | func isTemporaryNetError(err error) bool { 22 | if nerr, ok := err.(net.Error); ok && nerr.Temporary() { 23 | return true 24 | } else { 25 | return false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /connection_manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestCreateConnectionManager(t *testing.T) { 22 | _, err := newConnectionManager(nil) 23 | if err == nil { 24 | t.Error("expected non-nil error when creating without options") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rpb/riak_dt/riak_dt_misc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak_dt 16 | 17 | // DtUpdateReq 18 | 19 | func (m *DtUpdateReq) SetType(bt []byte) { 20 | m.Type = bt 21 | } 22 | 23 | func (m *DtUpdateReq) BucketIsRequired() bool { 24 | return true 25 | } 26 | 27 | func (m *DtUpdateReq) KeyIsRequired() bool { 28 | return false 29 | } 30 | 31 | // DtFetchReq 32 | 33 | func (m *DtFetchReq) SetType(bt []byte) { 34 | m.Type = bt 35 | } 36 | 37 | func (m *DtFetchReq) BucketIsRequired() bool { 38 | return true 39 | } 40 | 41 | func (m *DtFetchReq) KeyIsRequired() bool { 42 | return true 43 | } 44 | -------------------------------------------------------------------------------- /rpb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func rpbValidateResp(data []byte, expected byte) (err error) { 22 | if len(data) == 0 { 23 | err = ErrZeroLength 24 | return 25 | } 26 | if err = rpbEnsureCode(expected, data[0]); err != nil { 27 | return 28 | } 29 | return 30 | } 31 | 32 | func rpbEnsureCode(expected byte, actual byte) (err error) { 33 | if expected != actual { 34 | err = newClientError(fmt.Sprintf("expected response code %d, got: %d", expected, actual), nil) 35 | } 36 | return 37 | } 38 | 39 | func rpbBytes(s string) []byte { 40 | if s == "" { 41 | return nil 42 | } 43 | return []byte(s) 44 | } 45 | -------------------------------------------------------------------------------- /object_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestConvertObjectToRpbNilFields_GH92(t *testing.T) { 22 | v := "this is a value" 23 | ro := &Object{ 24 | Value: []byte(v), 25 | } 26 | if rpb, err := toRpbContent(ro); err != nil { 27 | t.Fatalf("error converting to rpb: %v", err) 28 | } else { 29 | if rpb == nil { 30 | t.Fatal("protobuf is nil") 31 | } 32 | if nil != rpb.GetContentType() { 33 | t.Errorf("expected nil ContentType") 34 | } 35 | if nil != rpb.GetCharset() { 36 | t.Errorf("expected nil Charset") 37 | } 38 | if nil != rpb.GetContentEncoding() { 39 | t.Errorf("expected nil ContentEncoding") 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "strconv" 21 | "testing" 22 | ) 23 | 24 | func BenchmarkPuttingManyObjects(b *testing.B) { 25 | cluster := integrationTestsBuildCluster() 26 | defer func() { 27 | if err := cluster.Stop(); err != nil { 28 | b.Error(err) 29 | } 30 | }() 31 | 32 | for i := 0; i < b.N; i++ { 33 | obj := getBasicObject() 34 | obj.Value = randomBytes 35 | 36 | store, err := NewStoreValueCommandBuilder(). 37 | WithBucket("memprofile"). 38 | WithKey(strconv.Itoa(i)). 39 | WithContent(obj). 40 | Build() 41 | if err != nil { 42 | b.Fatal(err) 43 | } 44 | if err := cluster.Execute(store); err != nil { 45 | b.Fatal(err) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /locatable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | proto "github.com/golang/protobuf/proto" 19 | ) 20 | 21 | type rpbLocatable interface { 22 | GetType() []byte 23 | SetType(bt []byte) // NB: bt == bucket type 24 | BucketIsRequired() bool 25 | GetBucket() []byte 26 | KeyIsRequired() bool 27 | GetKey() []byte 28 | } 29 | 30 | func validateLocatable(msg proto.Message) error { 31 | l := msg.(rpbLocatable) 32 | if l.BucketIsRequired() { 33 | if bucket := l.GetBucket(); len(bucket) == 0 { 34 | return ErrBucketRequired 35 | } 36 | } 37 | if l.KeyIsRequired() { 38 | if key := l.GetKey(); len(key) == 0 { 39 | return ErrKeyRequired 40 | } 41 | } 42 | if bucketType := l.GetType(); len(bucketType) == 0 { 43 | l.SetType([]byte(defaultBucketType)) 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: go 4 | go: 5 | - 1.6.x 6 | - 1.7.x 7 | - master 8 | env: 9 | global: 10 | - RIAK_HOST=localhost 11 | - RIAK_PORT=8087 12 | matrix: 13 | - RIAK_DOWNLOAD_URL=http://s3.amazonaws.com/downloads.basho.com/riak/2.0/2.0.7/ubuntu/trusty/riak_2.0.7-1_amd64.deb 14 | - RIAK_DOWNLOAD_URL=http://s3.amazonaws.com/downloads.basho.com/riak/2.2/2.2.0/ubuntu/trusty/riak_2.2.0-1_amd64.deb 15 | before_script: 16 | - jdk_switcher use oraclejdk8 17 | - sudo ./tools/travis-ci/riak-install -d "$RIAK_DOWNLOAD_URL" 18 | - sudo ./tools/setup-riak -s 19 | script: 20 | - sudo riak-admin security disable 21 | - make test 22 | - sudo riak-admin security enable 23 | - make security-test 24 | notifications: 25 | slack: 26 | secure: czHGTocw0ERiYsnE+CsW6FRs0PM1YU7fleorn5QGxi4gAjbvk+PVEr4YgiJutzJWs+AMCRn8MdIHRTLUcCVewmXDKeLUBRf4Nj8pSgEsOqEHK+ftE35Gy5jNBqGCMr90YF5rSaNCkBFnlW+cy45Z2fvVoy/YkKjlQ2u7A0B1fvZVCHCRT7R3rKkaTApMEp/2gKxKL+7tcITTd0Jh1YdMurhEIo/CXspv57fcerCiRI4vdVj9iup/ZcbvdW4wx5557alvvj4uYntLbsj94cY6ox5t/EEehy8HOBn7pDUAwAbfadQ574EhZMHK0EZMnS3KHMHKf7/xP5inMfplNbIqqMMQ4yHH1vj49l2WdmVOJPWPJs5y9XNV5q9LDkJnp+ZgVLSYBLgzSnzCjJny/p5HH5lUAuaEzOPrif6rOD7zO0swWZDim5kQv5MKC5HK/7BJeLbT7hFHFTZ6gmzB4DFBSiSCfIYRdCrxylJnwiRT5yATKZfVQDBbFs9bwgeDNW3wXLtpZy+0Lx/q1fWi2Z0ZyuvQzOYSt0fLUXMoJILWmXSpWVSFpW4f6tl8kmEStPPPs5DnNOLlc8haZiaIfX9vpeYWBMMZBTl0Gs/ddCbzROTvvInJjkR1liJXmpKtzDYSCafy5ZUYPxSyg8s/mQVAhm+7FMZ/KC6f9gxonaw8oM0= 27 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Riak Go Client 2 | Copyright 2015 Basho Technologies, Inc. 3 | 4 | This product includes software developed by 5 | Basho Technologies, Inc (http://basho.com) 6 | 7 | ------------------------------------------------------------------------ 8 | 9 | Backoff 10 | https://github.com/basho/backoff (fork) 11 | https://github.com/jpillora/backoff 12 | 13 | MIT License 14 | 15 | Copyright 2015 Jaime Pillora 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a 18 | copy of this software and associated documentation files (the 19 | 'Software'), to deal in the Software without restriction, including 20 | without limitation the rights to use, copy, modify, merge, publish, 21 | distribute, sublicense, and/or sell copies of the Software, and to 22 | permit persons to whom the Software is furnished to do so, subject to 23 | the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included 26 | in all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS 29 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 30 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 31 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 32 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 33 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 34 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 35 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func TestSplitRemoteAddress(t *testing.T) { 23 | s := strings.SplitN(defaultRemoteAddress, ":", 2) 24 | if expected, actual := "127.0.0.1", s[0]; expected != actual { 25 | t.Errorf("expected %v, actual %v", expected, actual) 26 | } 27 | if expected, actual := "8087", s[1]; expected != actual { 28 | t.Errorf("expected %v, actual %v", expected, actual) 29 | } 30 | } 31 | 32 | func TestNewClientWithInvalidData(t *testing.T) { 33 | opts := &NewClientOptions{ 34 | RemoteAddresses: []string{ 35 | "FOO:BAR:BAZ", 36 | }, 37 | } 38 | c, err := NewClient(opts) 39 | if err == nil { 40 | t.Errorf("expected non-nil error, %v", c) 41 | } 42 | 43 | opts = &NewClientOptions{ 44 | RemoteAddresses: []string{ 45 | "127.0.0.1:FRAZZLE", 46 | }, 47 | } 48 | c, err = NewClient(opts) 49 | if err == nil { 50 | t.Errorf("expected non-nil error, %v", c) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /consts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "time" 20 | ) 21 | 22 | const ( 23 | threeSeconds = time.Second * 3 24 | fiveSeconds = time.Second * 5 25 | tenSeconds = time.Second * 10 26 | defaultBucketType = "default" 27 | defaultRemotePort = uint16(8087) 28 | defaultMinConnections = uint16(1) 29 | defaultMaxConnections = uint16(256) 30 | defaultIdleExpirationInterval = fiveSeconds 31 | defaultIdleTimeout = tenSeconds 32 | defaultConnectTimeout = threeSeconds 33 | defaultRequestTimeout = fiveSeconds 34 | defaultHealthCheckInterval = 125 * time.Millisecond 35 | defaultExecutionAttempts = byte(3) 36 | defaultQueueExecutionInterval = 125 * time.Millisecond 37 | defaultInitBuffer = 2048 38 | defaultTempNetErrorRetries = uint16(0) 39 | ) 40 | 41 | var defaultRemoteAddress = fmt.Sprintf("127.0.0.1:%d", defaultRemotePort) 42 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | rpb_riak "github.com/basho/riak-go-client/rpb/riak" 22 | ) 23 | 24 | func TestBuildRiakErrorFromRpbErrorResp(t *testing.T) { 25 | var errcode uint32 = 1 26 | errmsg := bytes.NewBufferString("this is an error") 27 | rpbErr := &rpb_riak.RpbErrorResp{ 28 | Errcode: &errcode, 29 | Errmsg: errmsg.Bytes(), 30 | } 31 | err := newRiakError(rpbErr) 32 | if riakError, ok := err.(RiakError); ok == true { 33 | if expected, actual := errcode, riakError.Errcode; expected != actual { 34 | t.Errorf("expected %v, got %v", expected, actual) 35 | } 36 | if expected, actual := "this is an error", riakError.Errmsg; expected != actual { 37 | t.Errorf("expected %v, got %v", expected, actual) 38 | } 39 | if expected, actual := "RiakError|1|this is an error", riakError.Error(); expected != actual { 40 | t.Errorf("expected %v, got %v", expected, actual) 41 | } 42 | } else { 43 | t.Error("error in type conversion") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /command_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "net" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestPing(t *testing.T) { 26 | var ( 27 | addr *net.TCPAddr 28 | err error 29 | conn *connection 30 | ) 31 | addr, err = net.ResolveTCPAddr("tcp4", getRiakAddress()) 32 | if err != nil { 33 | t.Error(err.Error()) 34 | } 35 | opts := &connectionOptions{ 36 | remoteAddress: addr, 37 | connectTimeout: time.Second * 5, 38 | requestTimeout: time.Millisecond * 500, 39 | } 40 | if conn, err = newConnection(opts); err == nil { 41 | if err = conn.connect(); err == nil { 42 | cmd := &PingCommand{} 43 | if expected, actual := false, conn.inFlight; expected != actual { 44 | t.Errorf("expected %v, got: %v", expected, actual) 45 | } 46 | if err = conn.execute(cmd); err == nil { 47 | if cmd.Success() != true { 48 | t.Error("ping did not return true") 49 | } 50 | } 51 | } 52 | } 53 | if err != nil { 54 | t.Error(err.Error()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /connection_manager_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "net" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestConnectionManagerDoesNotExpirePastMinConnections(t *testing.T) { 26 | minConnections := uint16(10) 27 | 28 | o := &testListenerOpts{ 29 | test: t, 30 | host: "127.0.0.1", 31 | port: 13340, 32 | } 33 | tl := newTestListener(o) 34 | tl.start() 35 | defer tl.stop() 36 | 37 | addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:13340") 38 | 39 | cmopts := &connectionManagerOptions{ 40 | addr: addr, 41 | minConnections: minConnections, 42 | maxConnections: 20, 43 | idleExpirationInterval: time.Millisecond * 500, 44 | idleTimeout: time.Millisecond * 10, 45 | } 46 | 47 | cm, err := newConnectionManager(cmopts) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | err = cm.start() 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | for i := 0; i < 10; i++ { 57 | time.Sleep(time.Millisecond * 250) 58 | if actual, expected := cm.connectionCounter.count(), minConnections; actual != expected { 59 | t.Errorf("got: %v, expected: %v", actual, expected) 60 | } 61 | } 62 | 63 | err = cm.stop() 64 | if err != nil { 65 | t.Error(err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /command_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | ) 21 | 22 | func TestEnqueueDequeueCommandsConcurrently(t *testing.T) { 23 | queueSize := uint16(64) 24 | queue := newQueue(queueSize) 25 | 26 | w := &sync.WaitGroup{} 27 | for i := uint16(0); i < queueSize; i++ { 28 | w.Add(1) 29 | go func() { 30 | cmd := &PingCommand{} 31 | async := &Async{ 32 | Command: cmd, 33 | } 34 | if err := queue.enqueue(async); err != nil { 35 | t.Error(err) 36 | } 37 | w.Done() 38 | }() 39 | } 40 | 41 | w.Wait() 42 | 43 | cmd := &PingCommand{} 44 | async := &Async{ 45 | Command: cmd, 46 | } 47 | if err := queue.enqueue(async); err == nil { 48 | t.Error("expected non-nil err when enqueueing one more command than max") 49 | } 50 | if expected, actual := false, queue.isEmpty(); expected != actual { 51 | t.Errorf("expected %v, got %v", expected, actual) 52 | } 53 | 54 | w = &sync.WaitGroup{} 55 | for i := uint16(0); i < queueSize; i++ { 56 | w.Add(1) 57 | go func() { 58 | cmd, err := queue.dequeue() 59 | if cmd == nil { 60 | t.Error("expected non-nil cmd") 61 | } 62 | if err != nil { 63 | t.Error("expected nil err") 64 | } 65 | w.Done() 66 | }() 67 | } 68 | 69 | w.Wait() 70 | 71 | if expected, actual := true, queue.isEmpty(); expected != actual { 72 | t.Errorf("expected %v, got %v", expected, actual) 73 | } 74 | 75 | queue.destroy() 76 | 77 | _, err := queue.dequeue() 78 | if err == nil { 79 | t.Error("expected non-nil err") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /client_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "net" 21 | "reflect" 22 | "testing" 23 | ) 24 | 25 | func TestNewClientWithPort(t *testing.T) { 26 | ports := []uint16{1234, 5678} 27 | for _, p := range ports { 28 | o := &testListenerOpts{ 29 | test: t, 30 | host: "127.0.0.1", 31 | port: p, 32 | } 33 | tl := newTestListener(o) 34 | tl.start() 35 | defer tl.stop() 36 | } 37 | 38 | opts := &NewClientOptions{ 39 | Port: 1234, 40 | RemoteAddresses: []string{ 41 | "127.0.0.1", 42 | "127.0.0.1:5678", 43 | "127.0.0.1", 44 | }, 45 | } 46 | c, err := NewClient(opts) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | var addr *net.TCPAddr 51 | addr, err = net.ResolveTCPAddr("tcp", "127.0.0.1:1234") 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | if expected, actual := true, reflect.DeepEqual(addr, c.cluster.nodes[0].addr); expected != actual { 56 | t.Errorf("expected %v, actual %v", expected, actual) 57 | } 58 | addr, err = net.ResolveTCPAddr("tcp", "127.0.0.1:5678") 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | if expected, actual := true, reflect.DeepEqual(addr, c.cluster.nodes[1].addr); expected != actual { 63 | t.Errorf("expected %v, actual %v", expected, actual) 64 | } 65 | addr, err = net.ResolveTCPAddr("tcp", "127.0.0.1:1234") 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | if expected, actual := true, reflect.DeepEqual(addr, c.cluster.nodes[2].addr); expected != actual { 70 | t.Errorf("expected %v, actual %v", expected, actual) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ##Contributing 2 | 3 | This repo's maintainers are engineers at Basho and we welcome your contribution to the project! 4 | 5 | ### An honest disclaimer 6 | 7 | Due to our obsession with stability and our rich ecosystem of users, community updates on this repo may take a little longer to review. 8 | The most helpful way to contribute is by reporting your experience through issues. Issues may not be updated while we review internally, but they're still incredibly appreciated. 9 | 10 | Thank you for being part of the community. 11 | 12 | ## How-to contribute to the Go client 13 | 14 | **_IMPORTANT_**: This is an open source project licensed under the Apache 2.0 License. We encourage contributions to the project from the community. We ask that you keep in mind these considerations when planning your contribution. 15 | 16 | * Whether your contribution is for a bug fix or a feature request, **create an [Issue](https://github.com/basho/riak-go-client/issues)** and let us know what you are thinking. 17 | * **For bugs**, if you have already found a fix, feel free to submit a Pull Request referencing the Issue you created. 18 | * **For feature requests**, we want to improve upon the library incrementally which means small changes at a time. In order ensure your PR can be reviewed in a timely manner, please keep PRs small, e.g. <10 files and <500 lines changed. If you think this is unrealistic, then mention that within the Issue and we can discuss it. 19 | 20 | ### Pull Request Process 21 | 22 | Here’s how to get started: 23 | 24 | * Fork the appropriate sub-projects that are affected by your change. 25 | * Create a topic branch for your change and checkout that branch. 26 | `git checkout -b some-topic-branch` 27 | * Make your changes and run the test suite if one is provided. (see below) 28 | * Commit your changes and push them to your fork. 29 | * Open pull-requests for the appropriate projects. 30 | * Contributors will review your pull request, suggest changes, and merge it when it’s ready and/or offer feedback. 31 | * To report a bug or issue, please open a new issue against this repository. 32 | 33 | You can [read the full guidelines for bug reporting and code contributions](http://docs.basho.com/riak/latest/community/bugs/) on the Riak Docs. 34 | -------------------------------------------------------------------------------- /async.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | "github.com/basho/backoff" 22 | ) 23 | 24 | // Async object is used to pass required arguments to execute a Command asynchronously 25 | type Async struct { 26 | Command Command 27 | Done chan Command 28 | Wait *sync.WaitGroup 29 | Error error 30 | rb *backoff.Backoff // rb - Retry Backoff 31 | enqueuedAt time.Time 32 | executeAt time.Time 33 | qb *backoff.Backoff // qb - Queue Backoff 34 | } 35 | 36 | func (a *Async) onExecute() { 37 | if a.rb == nil { 38 | a.rb = &backoff.Backoff{ 39 | Jitter: true, 40 | } 41 | } else { 42 | a.rb.Reset() 43 | } 44 | } 45 | 46 | func (a *Async) onRetry() { 47 | d := a.rb.Duration() 48 | logDebug("[Async]", "onRetry cmd: %s sleep: %v", a.Command.Name(), d) 49 | time.Sleep(d) 50 | } 51 | 52 | func (a *Async) onEnqueued() { 53 | if a.qb == nil { 54 | a.enqueuedAt = time.Now() 55 | a.qb = &backoff.Backoff{ 56 | Factor: 1.5, 57 | Jitter: true, 58 | } 59 | } 60 | a.executeAt = a.enqueuedAt.Add(a.qb.Duration()) 61 | } 62 | 63 | func (a *Async) done(err error) { 64 | if err != nil { 65 | // TODO FUTURE evaluate debug logging 66 | // logDebugln("[Async]", "done error:", err) 67 | a.Error = err 68 | } 69 | if a.Done != nil { 70 | // TODO FUTURE evaluate debug logging 71 | // logDebug("[Async]", "signaling a.Done channel with '%s'", a.Command.Name()) 72 | a.Done <- a.Command 73 | } 74 | if a.Wait != nil { 75 | // TODO FUTURE evaluate debug logging 76 | // logDebug("[Async]", "signaling a.Wait WaitGroup for '%s'", a.Command.Name()) 77 | a.Wait.Done() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /logging_test.go: -------------------------------------------------------------------------------- 1 | package riak 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestLog(t *testing.T) { 12 | EnableDebugLogging = true 13 | 14 | tests := []struct { 15 | setLoggerFunc func(*log.Logger) 16 | logFunc func(string, string, ...interface{}) 17 | prefix string 18 | }{ 19 | { 20 | SetErrorLogger, 21 | logError, 22 | "[ERROR]", 23 | }, 24 | { 25 | SetLogger, 26 | logWarn, 27 | "[WARNING]", 28 | }, 29 | { 30 | SetLogger, 31 | logDebug, 32 | "[DEBUG]", 33 | }, 34 | } 35 | 36 | for _, tt := range tests { 37 | buf := &bytes.Buffer{} 38 | logger := log.New(buf, "", log.LstdFlags) 39 | tt.setLoggerFunc(logger) 40 | tt.logFunc("[test]", "Hello %s!", "World") 41 | 42 | actual := buf.String() 43 | suffix := fmt.Sprintf("%s %s", tt.prefix, "[test] Hello World!\n") 44 | 45 | if !strings.HasSuffix(actual, suffix) { 46 | t.Errorf("Expected %s to end with %s", actual, suffix) 47 | } 48 | } 49 | } 50 | 51 | func TestLogln(t *testing.T) { 52 | EnableDebugLogging = true 53 | 54 | tests := []struct { 55 | setLoggerFunc func(*log.Logger) 56 | logFunc func(string, ...interface{}) 57 | prefix string 58 | }{ 59 | { 60 | SetErrorLogger, 61 | logErrorln, 62 | "[ERROR]", 63 | }, 64 | { 65 | SetLogger, 66 | logWarnln, 67 | "[WARNING]", 68 | }, 69 | { 70 | SetLogger, 71 | logDebugln, 72 | "[DEBUG]", 73 | }, 74 | } 75 | 76 | for _, tt := range tests { 77 | buf := &bytes.Buffer{} 78 | logger := log.New(buf, "", log.LstdFlags) 79 | tt.setLoggerFunc(logger) 80 | tt.logFunc("[test]", "Hello", "World!") 81 | 82 | actual := buf.String() 83 | suffix := fmt.Sprintf("%s %s", tt.prefix, "[test] [Hello World!]\n") 84 | 85 | if !strings.HasSuffix(actual, suffix) { 86 | t.Errorf("Expected %s to end with %s", actual, suffix) 87 | } 88 | } 89 | } 90 | 91 | func TestDebugDisabled(t *testing.T) { 92 | EnableDebugLogging = false 93 | 94 | buf := &bytes.Buffer{} 95 | logger := log.New(buf, "", log.LstdFlags) 96 | SetLogger(logger) 97 | 98 | logDebug("[test]", "Hello %s!", "World") 99 | logDebugln("[test]", "Hello", "World") 100 | 101 | actual := buf.String() 102 | 103 | if len(actual) != 0 { 104 | t.Errorf("Debug was disabled but got %s", actual) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package riak provides the interfaces needed to interact with Riak KV using 17 | Protocol Buffers. Riak KV is a distributed key-value datastore designed to be 18 | fault tolerant, scalable, and flexible. 19 | 20 | Currently, this library was written for and tested against Riak KV 2.0+. 21 | 22 | TL;DR; 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | riak "github.com/basho/riak-go-client" 28 | ) 29 | 30 | func main() { 31 | nodeOpts := &riak.NodeOptions{ 32 | RemoteAddress: "127.0.0.1:8087", 33 | } 34 | 35 | var node *riak.Node 36 | var err error 37 | if node, err = riak.NewNode(nodeOpts); err != nil { 38 | fmt.Println(err.Error()) 39 | os.Exit(1) 40 | } 41 | 42 | nodes := []*riak.Node{node} 43 | opts := &riak.ClusterOptions{ 44 | Nodes: nodes, 45 | } 46 | 47 | cluster, err := riak.NewCluster(opts) 48 | if err != nil { 49 | fmt.Println(err.Error()) 50 | os.Exit(1) 51 | } 52 | 53 | defer func() { 54 | if err := cluster.Stop(); err != nil { 55 | fmt.Println(err.Error()) 56 | os.Exit(1) 57 | } 58 | }() 59 | 60 | if err := cluster.Start(); err != nil { 61 | fmt.Println(err.Error()) 62 | os.Exit(1) 63 | } 64 | 65 | obj := &riak.Object{ 66 | ContentType: "text/plain", 67 | Charset: "utf-8", 68 | ContentEncoding: "utf-8", 69 | Value: []byte("this is a value in Riak"), 70 | } 71 | 72 | cmd, err := riak.NewStoreValueCommandBuilder(). 73 | WithBucket("testBucketName"). 74 | WithContent(obj). 75 | Build() 76 | if err != nil { 77 | fmt.Println(err.Error()) 78 | os.Exit(1) 79 | } 80 | 81 | if err := cluster.Execute(cmd); err != nil { 82 | fmt.Println(err.Error()) 83 | os.Exit(1) 84 | } 85 | 86 | svc := cmd.(*riak.StoreValueCommand) 87 | rsp := svc.Response 88 | fmt.Println(rsp.GeneratedKey) 89 | } 90 | */ 91 | package riak 92 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install-deps 2 | .PHONY: unit-test integration-test security-test test fmt help 3 | .PHONY: fmt lint protogen release 4 | 5 | PROJDIR = $(realpath $(CURDIR)) 6 | PROTOC_VERSION := $(shell protoc --version) 7 | 8 | export RIAK_HOST = localhost 9 | export RIAK_PORT = 8087 10 | 11 | all: install-deps lint test 12 | 13 | install-deps: 14 | cd $(PROJDIR) && go get -t github.com/basho/riak-go-client/... 15 | 16 | lint: install-deps 17 | cd $(PROJDIR) && go tool vet -shadow=true -shadowstrict=true $(PROJDIR) 18 | cd $(PROJDIR) && go vet github.com/basho/riak-go-client/... 19 | 20 | unit-test: lint 21 | cd $(PROJDIR) && go test -v 22 | 23 | integration-test: lint 24 | cd $(PROJDIR) && go test -v -tags='integration timeseries' 25 | 26 | timeseries-test: lint 27 | cd $(PROJDIR) && go test -v -tags=timeseries 28 | 29 | security-test: lint 30 | cd $(PROJDIR) && go test -v -tags=security 31 | 32 | test: integration-test 33 | 34 | fmt: 35 | cd $(PROJDIR) && gofmt -s -w . 36 | 37 | protogen: 38 | ifeq ($(PROTOC_VERSION),) 39 | $(error The protoc command is required to parse proto files) 40 | endif 41 | ifneq ($(PROTOC_VERSION),libprotoc 2.6.1) 42 | $(error protoc must be version 2.6.1) 43 | endif 44 | $(PROJDIR)/build/protogen $(PROJDIR) 45 | 46 | release: 47 | ifeq ($(VERSION),) 48 | $(error VERSION must be set to deploy this code) 49 | endif 50 | ifeq ($(RELEASE_GPG_KEYNAME),) 51 | $(error RELEASE_GPG_KEYNAME must be set to deploy this code) 52 | endif 53 | @$(PROJDIR)/tools/build/publish $(VERSION) master validate 54 | @git tag --sign -a "$(VERSION)" -m "riak-go-client $(VERSION)" --local-user "$(RELEASE_GPG_KEYNAME)" 55 | @git push --tags 56 | @$(PROJDIR)/tools/build/publish $(VERSION) master 'Riak Go Client' 'riak-go-client' 57 | 58 | help: 59 | @echo '' 60 | @echo ' Targets:' 61 | @echo '----------------------------------------------------------' 62 | @echo ' all - Run everything ' 63 | @echo ' fmt - Format code ' 64 | @echo ' lint - Run "go vet" ' 65 | @echo ' test - Run unit & integration tests ' 66 | @echo ' unit-test - Run unit tests ' 67 | @echo ' integration-test - Run integration tests ' 68 | @echo ' timeseries-test - Run timeseries tests ' 69 | @echo ' security-test - Run security tests ' 70 | @echo '----------------------------------------------------------' 71 | @echo '' 72 | -------------------------------------------------------------------------------- /states.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | ) 21 | 22 | type state byte 23 | 24 | type stateful interface { 25 | fmt.Stringer 26 | setStateDesc(desc ...string) 27 | isCurrentState(st state) (rv bool) 28 | isStateLessThan(st state) (rv bool) 29 | setState(st state) 30 | getState() (st state) 31 | stateCheck(allowed ...state) (err error) 32 | } 33 | 34 | type stateData struct { 35 | sync.RWMutex 36 | stateVal state 37 | stateDesc []string 38 | setStateFunc func(sd *stateData, st state) 39 | } 40 | 41 | var defaultSetStateFunc = func(sd *stateData, st state) { 42 | sd.stateVal = st 43 | } 44 | 45 | func (s *stateData) initStateData(desc ...string) { 46 | s.stateDesc = desc 47 | s.setStateFunc = defaultSetStateFunc 48 | } 49 | 50 | func (s *stateData) String() string { 51 | stateIdx := int(s.stateVal) 52 | if len(s.stateDesc) > stateIdx { 53 | return s.stateDesc[stateIdx] 54 | } else { 55 | return fmt.Sprintf("STATE_%v", stateIdx) 56 | } 57 | } 58 | 59 | func (s *stateData) isCurrentState(st state) bool { 60 | s.RLock() 61 | defer s.RUnlock() 62 | return s.stateVal == st 63 | } 64 | 65 | func (s *stateData) isStateLessThan(st state) bool { 66 | s.RLock() 67 | defer s.RUnlock() 68 | return s.stateVal < st 69 | } 70 | 71 | func (s *stateData) getState() state { 72 | s.RLock() 73 | defer s.RUnlock() 74 | return s.stateVal 75 | } 76 | 77 | func (s *stateData) setState(st state) { 78 | s.Lock() 79 | defer s.Unlock() 80 | s.setStateFunc(s, st) 81 | } 82 | 83 | func (s *stateData) stateCheck(allowed ...state) error { 84 | s.RLock() 85 | defer s.RUnlock() 86 | stateAllowed := false 87 | for _, st := range allowed { 88 | if s.stateVal == st { 89 | stateAllowed = true 90 | break 91 | } 92 | } 93 | if !stateAllowed { 94 | return fmt.Errorf("Illegal State - required %v: current: %v", allowed, s.stateVal) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /node_manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | // NodeManager enforces the structure needed to if going to implement your own NodeManager 22 | type NodeManager interface { 23 | ExecuteOnNode(nodes []*Node, command Command, previousNode *Node) (bool, error) 24 | } 25 | 26 | var ErrDefaultNodeManagerRequiresNode = newClientError("Must pass at least one node to default node manager", nil) 27 | 28 | type defaultNodeManager struct { 29 | nodeIndex int 30 | sync.RWMutex 31 | } 32 | 33 | // ExecuteOnNode selects a Node from the pool and executes the provided Command on that Node. The 34 | // defaultNodeManager uses a simple round robin approach to distributing load 35 | func (nm *defaultNodeManager) ExecuteOnNode(nodes []*Node, command Command, previous *Node) (bool, error) { 36 | if nodes == nil { 37 | panic("[defaultNodeManager] nil nodes argument") 38 | } 39 | if len(nodes) == 0 || nodes[0] == nil { 40 | return false, ErrDefaultNodeManagerRequiresNode 41 | } 42 | 43 | var err error 44 | executed := false 45 | 46 | nm.RLock() 47 | startingIndex := nm.nodeIndex 48 | nm.RUnlock() 49 | 50 | for { 51 | nm.Lock() 52 | if nm.nodeIndex >= len(nodes) { 53 | nm.nodeIndex = 0 54 | } 55 | node := nodes[nm.nodeIndex] 56 | nm.nodeIndex++ 57 | nm.Unlock() 58 | 59 | // don't try the same node twice in a row if we have multiple nodes 60 | if len(nodes) > 1 && previous != nil && previous == node { 61 | continue 62 | } 63 | 64 | executed, err = node.execute(command) 65 | if executed == true { 66 | logDebug("[DefaultNodeManager]", "executed '%s' on node '%s', err '%v'", command.Name(), node, err) 67 | break 68 | } 69 | 70 | nm.RLock() 71 | if startingIndex == nm.nodeIndex { 72 | nm.RUnlock() 73 | // logDebug("[DefaultNodeManager]", "startingIndex %d nm.nodeIndex %d", startingIndex, nm.nodeIndex) 74 | break 75 | } 76 | nm.RUnlock() 77 | } 78 | 79 | return executed, err 80 | } 81 | -------------------------------------------------------------------------------- /queue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | ) 21 | 22 | func TestReadFromEmptyQueue(t *testing.T) { 23 | q := newQueue(1) 24 | v, err := q.dequeue() 25 | if err != nil { 26 | t.Error("expected nil error when reading from empty queue") 27 | } 28 | if v != nil { 29 | t.Error("expected nil value when reading from empty queue") 30 | } 31 | if expected, actual := uint16(0), q.count(); expected != actual { 32 | t.Errorf("expected %v, got %v", expected, actual) 33 | } 34 | } 35 | 36 | func TestIterateEmptyQueue(t *testing.T) { 37 | count := uint16(128) 38 | q := newQueue(count) 39 | executed := false 40 | var f = func(val interface{}) (bool, bool) { 41 | executed = true 42 | if val == nil { 43 | return true, true 44 | } else { 45 | return false, true 46 | } 47 | } 48 | err := q.iterate(f) 49 | if err != nil { 50 | t.Error("expected nil error when iterating queue") 51 | } 52 | if expected, actual := false, executed; expected != actual { 53 | t.Errorf("expected %v, got %v", expected, actual) 54 | } 55 | if expected, actual := uint16(0), q.count(); expected != actual { 56 | t.Errorf("expected %v, got %v", expected, actual) 57 | } 58 | } 59 | 60 | func TestConcurrentIterateQueue(t *testing.T) { 61 | count := uint16(2) 62 | wg := &sync.WaitGroup{} 63 | q := newQueue(count + 2) // make room for 666 64 | for i := uint16(0); i < count; i++ { 65 | q.enqueue(i) 66 | } 67 | 68 | wg_inner := &sync.WaitGroup{} 69 | for i := uint16(0); i < count; i++ { 70 | wg.Add(1) 71 | go func() { 72 | var f = func(val interface{}) (bool, bool) { 73 | wg_inner.Add(1) 74 | go func() { 75 | q.enqueue(666) 76 | wg_inner.Done() 77 | }() 78 | return false, true 79 | } 80 | err := q.iterate(f) 81 | if err != nil { 82 | t.Error("expected nil error when iterating queue") 83 | } 84 | wg.Done() 85 | }() 86 | } 87 | 88 | wg.Wait() 89 | wg_inner.Wait() 90 | 91 | if expected, actual := uint16(4), q.count(); expected != actual { 92 | t.Errorf("expected %v, got %v", expected, actual) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /rpb/riak/riak_misc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | // RpbGetBucketTypeReq 18 | 19 | func (m *RpbGetBucketTypeReq) GetKey() []byte { 20 | return nil 21 | } 22 | 23 | func (m *RpbGetBucketTypeReq) SetType(bt []byte) { 24 | m.Type = bt 25 | } 26 | 27 | func (m *RpbGetBucketTypeReq) BucketIsRequired() bool { 28 | return false 29 | } 30 | 31 | func (m *RpbGetBucketTypeReq) GetBucket() []byte { 32 | return nil 33 | } 34 | 35 | func (m *RpbGetBucketTypeReq) KeyIsRequired() bool { 36 | return false 37 | } 38 | 39 | // RpbGetBucketReq 40 | 41 | func (m *RpbGetBucketReq) GetKey() []byte { 42 | return nil 43 | } 44 | 45 | func (m *RpbGetBucketReq) SetType(bt []byte) { 46 | m.Type = bt 47 | } 48 | 49 | func (m *RpbGetBucketReq) BucketIsRequired() bool { 50 | return true 51 | } 52 | 53 | func (m *RpbGetBucketReq) KeyIsRequired() bool { 54 | return false 55 | } 56 | 57 | // RpbSetBucketTypeReq 58 | 59 | func (m *RpbSetBucketTypeReq) GetKey() []byte { 60 | return nil 61 | } 62 | 63 | func (m *RpbSetBucketTypeReq) SetType(bt []byte) { 64 | m.Type = bt 65 | } 66 | 67 | func (m *RpbSetBucketTypeReq) BucketIsRequired() bool { 68 | return false 69 | } 70 | 71 | func (m *RpbSetBucketTypeReq) GetBucket() []byte { 72 | return nil 73 | } 74 | 75 | func (m *RpbSetBucketTypeReq) KeyIsRequired() bool { 76 | return false 77 | } 78 | 79 | // RpbSetBucketReq 80 | 81 | func (m *RpbSetBucketReq) GetKey() []byte { 82 | return nil 83 | } 84 | 85 | func (m *RpbSetBucketReq) SetType(bt []byte) { 86 | m.Type = bt 87 | } 88 | 89 | func (m *RpbSetBucketReq) BucketIsRequired() bool { 90 | return true 91 | } 92 | 93 | func (m *RpbSetBucketReq) KeyIsRequired() bool { 94 | return false 95 | } 96 | 97 | // RpbResetBucketReq 98 | 99 | func (m *RpbResetBucketReq) GetKey() []byte { 100 | return nil 101 | } 102 | 103 | func (m *RpbResetBucketReq) SetType(bt []byte) { 104 | m.Type = bt 105 | } 106 | 107 | func (m *RpbResetBucketReq) BucketIsRequired() bool { 108 | return true 109 | } 110 | 111 | func (m *RpbResetBucketReq) KeyIsRequired() bool { 112 | return false 113 | } 114 | -------------------------------------------------------------------------------- /RELNOTES.md: -------------------------------------------------------------------------------- 1 | 3 | 4 | Release Notes 5 | ============= 6 | * `1.9.1` - [Milestone](https://github.com/basho/riak-go-client/issues?q=milestone%3Ariak-go-client-1.9.1) 7 | * `1.9.0` - [Milestone](https://github.com/basho/riak-go-client/issues?q=milestone%3Ariak-go-client-1.9.0) 8 | * `1.8.0` - [Milestone](https://github.com/basho/riak-go-client/issues?q=milestone%3Ariak-go-client-1.8.0) 9 | * `1.7.0` - Following PRs included: 10 | * [TimeSeries support](https://github.com/basho/riak-go-client/pull/68) 11 | * `1.6.0` - Following PRs included: 12 | * [Add `NodeOptions` test and concurrent commands test for `MaxConnections`](https://github.com/basho/riak-go-client/pull/61) 13 | * [Security auth fixes](https://github.com/basho/riak-go-client/pull/60) 14 | * [Use request or command timeout as applicable](https://github.com/basho/riak-go-client/pull/57) 15 | * [Re-tryable vs non-re-tryable commands](https://github.com/basho/riak-go-client/pull/56) 16 | * [Re-try reads on temporary network errors, bug fixes in error situations](https://github.com/basho/riak-go-client/pull/52) 17 | * `1.5.1` (DEPRECATED RELEASE) - Following PRs addressed: 18 | * [Improve connection error handling](https://github.com/basho/riak-go-client/pull/48) 19 | * `1.5.0` - Following PRs addressed: 20 | * [Add `FetchBucketTypePropsCommand` and `StoreBucketTypePropsCommand`](https://github.com/basho/riak-go-client/pull/42) 21 | * `1.4.0` - Following issues / PRs addressed: 22 | * [Add `ResetBucketCommand`](https://github.com/basho/riak-go-client/pull/35) 23 | * [Legacy Counter support](https://github.com/basho/riak-go-client/pull/33) 24 | * `1.3.0` - Following issues / PRs addressed: 25 | * [Add `NoDefaultNode` option to `ClusterOptions`](https://github.com/basho/riak-go-client/pull/28) 26 | * [`ConnectionManager` / `NodeManager` fixes](https://github.com/basho/riak-go-client/pull/25) 27 | * [`ConnectionManager` expiration fix](https://github.com/basho/riak-go-client/issues/23) 28 | * `1.2.0` - Following issues / PRs addressed: 29 | * [Conflict resolver not being passed to Fetch/Store-ValueCommand](https://github.com/basho/riak-go-client/issues/21) 30 | * [Reduce exported API](https://github.com/basho/riak-go-client/pull/20) 31 | * [Modify ClientError to trap an inner error if necessary](https://github.com/basho/riak-go-client/pull/19) 32 | * `1.1.0` - Following issues / PRs addressed: 33 | * [Issues with incrementing counters within Maps](https://github.com/basho/riak-go-client/issues/17) 34 | * [Extra goroutine in Execute](https://github.com/basho/riak-go-client/issues/16) 35 | * [Execute does not return error correctly](https://github.com/basho/riak-go-client/isues/15) 36 | * `1.0.0` - Initial release with Riak 2.0 support. 37 | * `1.0.0-beta11 - Initial beta release with Riak 2 support. Command queuing and retrying not implemented yet. 38 | 39 | -------------------------------------------------------------------------------- /logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | // Bare-bones logging to enable/disable debug logging 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | "os" 23 | ) 24 | 25 | // If true, debug messages will be written to the log 26 | var EnableDebugLogging = false 27 | 28 | var errLogger = log.New(os.Stderr, "", log.LstdFlags) 29 | var stdLogger = log.New(os.Stderr, "", log.LstdFlags) 30 | 31 | func init() { 32 | if debugEnvVar := os.Getenv("RIAK_GO_CLIENT_DEBUG"); debugEnvVar != "" { 33 | EnableDebugLogging = true 34 | } 35 | } 36 | 37 | // SetLogger sets the standard logger used for 38 | // WARN and DEBUG (if enabled) 39 | func SetLogger(logger *log.Logger) { 40 | stdLogger = logger 41 | } 42 | 43 | // SetErrorLogger sets the logger used for errors 44 | func SetErrorLogger(logger *log.Logger) { 45 | errLogger = logger 46 | } 47 | 48 | // logDebug writes formatted string debug messages using Printf only if debug logging is enabled 49 | func logDebug(source, format string, v ...interface{}) { 50 | if EnableDebugLogging { 51 | stdLogger.Printf(fmt.Sprintf("[DEBUG] %s %s", source, format), v...) 52 | } 53 | } 54 | 55 | // logDebugln writes string debug messages using Println 56 | func logDebugln(source string, v ...interface{}) { 57 | if EnableDebugLogging { 58 | stdLogger.Println("[DEBUG]", source, v) 59 | } 60 | } 61 | 62 | // logWarn writes formatted string warning messages using Printf 63 | func logWarn(source, format string, v ...interface{}) { 64 | stdLogger.Printf(fmt.Sprintf("[WARNING] %s %s", source, format), v...) 65 | } 66 | 67 | // logWarnln writes string warning messages using Println 68 | func logWarnln(source string, v ...interface{}) { 69 | stdLogger.Println("[WARNING]", source, v) 70 | } 71 | 72 | // logError writes formatted string error messages using Printf 73 | func logError(source, format string, v ...interface{}) { 74 | errLogger.Printf(fmt.Sprintf("[ERROR] %s %s", source, format), v...) 75 | } 76 | 77 | // logErr writes err.Error() using Println 78 | func logErr(source string, err error) { 79 | errLogger.Println("[ERROR]", source, err) 80 | } 81 | 82 | // logErrorln writes an error message using Println 83 | func logErrorln(source string, v ...interface{}) { 84 | errLogger.Println("[ERROR]", source, v) 85 | } 86 | -------------------------------------------------------------------------------- /gh_issues_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestGitHubIssue17UpdateMulipleCountersInMapAtOnce(t *testing.T) { 25 | cluster := integrationTestsBuildCluster() 26 | defer func() { 27 | if err := cluster.Stop(); err != nil { 28 | t.Error(err.Error()) 29 | } 30 | }() 31 | 32 | const bucketName = "github-17" 33 | var err error 34 | var cmd Command 35 | 36 | mapOp := &MapOperation{} 37 | mapOp.IncrementCounter("c1", 1) 38 | mapOp.IncrementCounter("c2", 2) 39 | mapOp.IncrementCounter("c3", 3) 40 | cmd, err = NewUpdateMapCommandBuilder(). 41 | WithBucketType(testMapBucketType). 42 | WithBucket(bucketName). 43 | WithMapOperation(mapOp). 44 | WithReturnBody(true). 45 | WithTimeout(time.Second * 20). 46 | Build() 47 | if err != nil { 48 | t.Fatal(err.Error()) 49 | } 50 | if err = cluster.Execute(cmd); err != nil { 51 | t.Fatal(err.Error()) 52 | } 53 | key := "unknown" 54 | if uc, ok := cmd.(*UpdateMapCommand); ok { 55 | if uc.Response == nil { 56 | t.Fatal("expected non-nil Response") 57 | } 58 | rsp := uc.Response 59 | if rsp.GeneratedKey == "" { 60 | t.Errorf("expected non-empty generated key") 61 | } else { 62 | key = rsp.GeneratedKey 63 | } 64 | } else { 65 | t.FailNow() 66 | } 67 | 68 | cmd, err = NewFetchMapCommandBuilder(). 69 | WithBucketType(testMapBucketType). 70 | WithBucket(bucketName). 71 | WithKey(key). 72 | Build() 73 | if err != nil { 74 | t.Fatal(err.Error()) 75 | } 76 | if err = cluster.Execute(cmd); err != nil { 77 | t.Fatal(err.Error()) 78 | } 79 | if fc, ok := cmd.(*FetchMapCommand); ok { 80 | if fc.Response == nil { 81 | t.Fatal("expected non-nil Response") 82 | } 83 | rsp := fc.Response 84 | if actual, expected := rsp.Map.Counters["c1"], int64(1); actual != expected { 85 | t.Errorf("actual %v, expected %v", actual, expected) 86 | } 87 | if actual, expected := rsp.Map.Counters["c2"], int64(2); actual != expected { 88 | t.Errorf("actual %v, expected %v", actual, expected) 89 | } 90 | if actual, expected := rsp.Map.Counters["c3"], int64(3); actual != expected { 91 | t.Errorf("actual %v, expected %v", actual, expected) 92 | } 93 | } else { 94 | t.FailNow() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | 20 | rpb_riak "github.com/basho/riak-go-client/rpb/riak" 21 | proto "github.com/golang/protobuf/proto" 22 | ) 23 | 24 | type RiakError struct { 25 | Errcode uint32 26 | Errmsg string 27 | } 28 | 29 | func newRiakError(rpb *rpb_riak.RpbErrorResp) (e error) { 30 | return RiakError{ 31 | Errcode: rpb.GetErrcode(), 32 | Errmsg: string(rpb.GetErrmsg()), 33 | } 34 | } 35 | 36 | func maybeRiakError(data []byte) (err error) { 37 | rpbMsgCode := data[0] 38 | if rpbMsgCode == rpbCode_RpbErrorResp { 39 | rpb := &rpb_riak.RpbErrorResp{} 40 | err = proto.Unmarshal(data[1:], rpb) 41 | if err == nil { 42 | // No error in Unmarshal, so construct RiakError 43 | err = newRiakError(rpb) 44 | } 45 | } 46 | return 47 | } 48 | 49 | func (e RiakError) Error() (s string) { 50 | return fmt.Sprintf("RiakError|%d|%s", e.Errcode, e.Errmsg) 51 | } 52 | 53 | // Client errors 54 | var ( 55 | ErrAddressRequired = newClientError("RemoteAddress is required in options", nil) 56 | ErrAuthMissingConfig = newClientError("[Connection] authentication is missing TLS config", nil) 57 | ErrAuthTLSUpgradeFailed = newClientError("[Connection] upgrading to TLS connection failed", nil) 58 | ErrBucketRequired = newClientError("Bucket is required", nil) 59 | ErrKeyRequired = newClientError("Key is required", nil) 60 | ErrNilOptions = newClientError("[Command] options must be non-nil", nil) 61 | ErrOptionsRequired = newClientError("Options are required", nil) 62 | ErrZeroLength = newClientError("[Command] 0 byte data response", nil) 63 | ErrTableRequired = newClientError("Table is required", nil) 64 | ErrQueryRequired = newClientError("Query is required", nil) 65 | ErrListingDisabled = newClientError("Bucket and key list operations are expensive and should not be used in production.", nil) 66 | ) 67 | 68 | type ClientError struct { 69 | Errmsg string 70 | InnerError error 71 | } 72 | 73 | func newClientError(errmsg string, innerError error) error { 74 | return ClientError{ 75 | Errmsg: errmsg, 76 | InnerError: innerError, 77 | } 78 | } 79 | 80 | func (e ClientError) Error() (s string) { 81 | if e.InnerError == nil { 82 | return fmt.Sprintf("ClientError|%s", e.Errmsg) 83 | } 84 | return fmt.Sprintf("ClientError|%s|InnerError|%v", e.Errmsg, e.InnerError) 85 | } 86 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import "sync" 18 | 19 | type queue struct { 20 | queueSize uint16 21 | queueChan chan interface{} 22 | sync.RWMutex 23 | } 24 | 25 | func newQueue(queueSize uint16) *queue { 26 | if queueSize == 0 { 27 | panic("[queue] size must be greater than zero!") 28 | } 29 | return &queue{ 30 | queueSize: queueSize, 31 | queueChan: make(chan interface{}, queueSize), 32 | } 33 | } 34 | 35 | func (q *queue) enqueue(v interface{}) error { 36 | if v == nil { 37 | panic("attempt to enqueue nil value") 38 | } 39 | q.Lock() 40 | defer q.Unlock() 41 | return q._do_enqueue(v) 42 | } 43 | 44 | func (q *queue) _do_enqueue(v interface{}) error { 45 | if len(q.queueChan) == int(q.queueSize) { 46 | return newClientError("attempt to enqueue when queue is full", nil) 47 | } 48 | q.queueChan <- v 49 | // logDebug("[queue]", "post-_do_ENqueue len: %v", len(q.queueChan)) 50 | return nil 51 | } 52 | 53 | func (q *queue) dequeue() (interface{}, error) { 54 | q.Lock() 55 | defer q.Unlock() 56 | return q._do_dequeue() 57 | } 58 | 59 | func (q *queue) _do_dequeue() (interface{}, error) { 60 | select { 61 | case v, ok := <-q.queueChan: 62 | if !ok { 63 | return nil, newClientError("attempt to dequeue from closed queue", nil) 64 | } 65 | // logDebug("[queue]", "post-DEqueue len: %v", len(q.queueChan)) 66 | return v, nil 67 | default: 68 | return nil, nil 69 | } 70 | } 71 | 72 | func (q *queue) iterate(f func(interface{}) (bool, bool)) error { 73 | q.Lock() 74 | defer q.Unlock() 75 | count := uint16(len(q.queueChan)) 76 | if count == 0 { 77 | return nil 78 | } 79 | c := uint16(0) 80 | for { 81 | c++ 82 | v, err := q._do_dequeue() 83 | if err != nil { 84 | return err 85 | } 86 | // NB: v may be nil if queue is currently empty 87 | brk, re_queue := f(v) 88 | if re_queue && v != nil { 89 | err = q._do_enqueue(v) 90 | if err != nil { 91 | return err 92 | } 93 | } 94 | if brk { 95 | break 96 | } 97 | if c == count { 98 | break 99 | } 100 | } 101 | return nil 102 | } 103 | 104 | func (q *queue) isEmpty() bool { 105 | q.RLock() 106 | defer q.RUnlock() 107 | return len(q.queueChan) == 0 108 | } 109 | 110 | func (q *queue) count() uint16 { 111 | q.RLock() 112 | defer q.RUnlock() 113 | return uint16(len(q.queueChan)) 114 | } 115 | 116 | func (q *queue) destroy() { 117 | close(q.queueChan) 118 | } 119 | -------------------------------------------------------------------------------- /states_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | type testStateData struct { 22 | stateData 23 | } 24 | 25 | const ( 26 | STATE_ONE state = iota 27 | STATE_TWO 28 | STATE_THREE 29 | STATE_FOUR 30 | 31 | OTHER_STATE_ONE 32 | OTHER_STATE_TWO 33 | OTHER_STATE_THREE 34 | ) 35 | 36 | func TestStateConsts(t *testing.T) { 37 | data1 := &testStateData{} 38 | data1.initStateData("STATE_ONE") 39 | data1.setState(STATE_ONE) 40 | 41 | data2 := &testStateData{} 42 | data2.initStateData("OTHER_STATE_ONE") 43 | data2.setState(OTHER_STATE_ONE) 44 | 45 | if s1, s2 := data1.getState(), data2.getState(); s1 == s2 { 46 | t.Errorf("whoops, %v equals %v", s1, s2) 47 | } 48 | } 49 | 50 | func TestStateData(t *testing.T) { 51 | data := &testStateData{} 52 | data.initStateData("STATE_TWO") 53 | data.setState(STATE_TWO) 54 | 55 | if expected, actual := true, data.isCurrentState(STATE_TWO); expected != actual { 56 | t.Errorf("expected %v, got %v", expected, actual) 57 | } 58 | 59 | if expected, actual := false, data.isCurrentState(STATE_ONE); expected != actual { 60 | t.Errorf("expected %v, got %v", expected, actual) 61 | } 62 | } 63 | 64 | func TestAllowedState(t *testing.T) { 65 | data := &testStateData{} 66 | data.initStateData("STATE_TWO") 67 | data.setState(STATE_TWO) 68 | 69 | if err := data.stateCheck(STATE_ONE, STATE_THREE); err == nil { 70 | t.Errorf("expected non-nil error, got %v", err) 71 | } 72 | } 73 | 74 | func TestStateDesc(t *testing.T) { 75 | data := &testStateData{} 76 | data.initStateData("STATE_ONE", "STATE_TWO", "STATE_THREE") 77 | 78 | data.setState(STATE_ONE) 79 | if expected, actual := "STATE_ONE", data.String(); expected != actual { 80 | t.Errorf("expected %v, got %v", expected, actual) 81 | } 82 | 83 | data.setState(STATE_TWO) 84 | if expected, actual := "STATE_TWO", data.String(); expected != actual { 85 | t.Errorf("expected %v, got %v", expected, actual) 86 | } 87 | 88 | data.setState(STATE_THREE) 89 | if expected, actual := "STATE_THREE", data.String(); expected != actual { 90 | t.Errorf("expected %v, got %v", expected, actual) 91 | } 92 | } 93 | 94 | func TestStateDescUnknown(t *testing.T) { 95 | data := &testStateData{} 96 | data.initStateData("STATE_ONE", "STATE_TWO", "STATE_THREE") 97 | data.setState(STATE_FOUR) 98 | 99 | if expected, actual := "STATE_3", data.String(); expected != actual { 100 | t.Errorf("expected %v, got %v", expected, actual) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rpb/riak_kv/riak_kv_misc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak_kv 16 | 17 | // RpbGetReq 18 | 19 | func (m *RpbGetReq) SetType(bt []byte) { 20 | m.Type = bt 21 | } 22 | 23 | func (m *RpbGetReq) BucketIsRequired() bool { 24 | return true 25 | } 26 | 27 | func (m *RpbGetReq) KeyIsRequired() bool { 28 | return true 29 | } 30 | 31 | // RpbPutReq 32 | 33 | func (m *RpbPutReq) SetType(bt []byte) { 34 | m.Type = bt 35 | } 36 | 37 | func (m *RpbPutReq) BucketIsRequired() bool { 38 | return true 39 | } 40 | 41 | func (m *RpbPutReq) KeyIsRequired() bool { 42 | return false 43 | } 44 | 45 | // RpbDelReq 46 | 47 | func (m *RpbDelReq) SetType(bt []byte) { 48 | m.Type = bt 49 | } 50 | 51 | func (m *RpbDelReq) BucketIsRequired() bool { 52 | return true 53 | } 54 | 55 | func (m *RpbDelReq) KeyIsRequired() bool { 56 | return true 57 | } 58 | 59 | // RpbListBucketsReq 60 | 61 | func (m *RpbListBucketsReq) SetType(bt []byte) { 62 | m.Type = bt 63 | } 64 | 65 | func (m *RpbListBucketsReq) BucketIsRequired() bool { 66 | return false 67 | } 68 | 69 | func (m *RpbListBucketsReq) GetBucket() []byte { 70 | return nil 71 | } 72 | 73 | func (m *RpbListBucketsReq) KeyIsRequired() bool { 74 | return false 75 | } 76 | 77 | func (m *RpbListBucketsReq) GetKey() []byte { 78 | return nil 79 | } 80 | 81 | // RpbListKeysReq 82 | 83 | func (m *RpbListKeysReq) SetType(bt []byte) { 84 | m.Type = bt 85 | } 86 | 87 | func (m *RpbListKeysReq) BucketIsRequired() bool { 88 | return true 89 | } 90 | 91 | func (m *RpbListKeysReq) KeyIsRequired() bool { 92 | return false 93 | } 94 | 95 | func (m *RpbListKeysReq) GetKey() []byte { 96 | return nil 97 | } 98 | 99 | // RpbGetBucketKeyPreflistReq 100 | 101 | func (m *RpbGetBucketKeyPreflistReq) SetType(bt []byte) { 102 | m.Type = bt 103 | } 104 | 105 | func (m *RpbGetBucketKeyPreflistReq) BucketIsRequired() bool { 106 | return true 107 | } 108 | 109 | func (m *RpbGetBucketKeyPreflistReq) KeyIsRequired() bool { 110 | return true 111 | } 112 | 113 | // RpbIndexReq 114 | 115 | func (m *RpbIndexReq) SetType(bt []byte) { 116 | m.Type = bt 117 | } 118 | 119 | func (m *RpbIndexReq) BucketIsRequired() bool { 120 | return true 121 | } 122 | 123 | func (m *RpbIndexReq) KeyIsRequired() bool { 124 | return false 125 | } 126 | 127 | // RpbCounterUpdateReq 128 | 129 | func (m *RpbCounterUpdateReq) SetType(bt []byte) { 130 | } 131 | 132 | func (m *RpbCounterUpdateReq) GetType() []byte { 133 | return nil 134 | } 135 | 136 | func (m *RpbCounterUpdateReq) BucketIsRequired() bool { 137 | return true 138 | } 139 | 140 | func (m *RpbCounterUpdateReq) KeyIsRequired() bool { 141 | return true 142 | } 143 | -------------------------------------------------------------------------------- /messages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | // Convert from csv with: 18 | // %s/\(\d\+\),\([^,]\+\),.*/const rpbCode_\2 byte = \1/ 19 | const rpbCode_RpbErrorResp byte = 0 20 | const rpbCode_RpbPingReq byte = 1 21 | const rpbCode_RpbPingResp byte = 2 22 | const rpbCode_RpbGetClientIdReq byte = 3 23 | const rpbCode_RpbGetClientIdResp byte = 4 24 | const rpbCode_RpbSetClientIdReq byte = 5 25 | const rpbCode_RpbSetClientIdResp byte = 6 26 | const rpbCode_RpbGetServerInfoReq byte = 7 27 | const rpbCode_RpbGetServerInfoResp byte = 8 28 | const rpbCode_RpbGetReq byte = 9 29 | const rpbCode_RpbGetResp byte = 10 30 | const rpbCode_RpbPutReq byte = 11 31 | const rpbCode_RpbPutResp byte = 12 32 | const rpbCode_RpbDelReq byte = 13 33 | const rpbCode_RpbDelResp byte = 14 34 | const rpbCode_RpbListBucketsReq byte = 15 35 | const rpbCode_RpbListBucketsResp byte = 16 36 | const rpbCode_RpbListKeysReq byte = 17 37 | const rpbCode_RpbListKeysResp byte = 18 38 | const rpbCode_RpbGetBucketReq byte = 19 39 | const rpbCode_RpbGetBucketResp byte = 20 40 | const rpbCode_RpbSetBucketReq byte = 21 41 | const rpbCode_RpbSetBucketResp byte = 22 42 | const rpbCode_RpbMapRedReq byte = 23 43 | const rpbCode_RpbMapRedResp byte = 24 44 | const rpbCode_RpbIndexReq byte = 25 45 | const rpbCode_RpbIndexResp byte = 26 46 | const rpbCode_RpbSearchQueryReq byte = 27 47 | const rpbCode_RpbSearchQueryResp byte = 28 48 | const rpbCode_RpbResetBucketReq byte = 29 49 | const rpbCode_RpbResetBucketResp byte = 30 50 | const rpbCode_RpbGetBucketTypeReq byte = 31 51 | const rpbCode_RpbSetBucketTypeReq byte = 32 52 | const rpbCode_RpbGetBucketKeyPreflistReq byte = 33 53 | const rpbCode_RpbGetBucketKeyPreflistResp byte = 34 54 | const rpbCode_RpbCSBucketReq byte = 40 55 | const rpbCode_RpbCSBucketResp byte = 41 56 | const rpbCode_RpbCounterUpdateReq byte = 50 57 | const rpbCode_RpbCounterUpdateResp byte = 51 58 | const rpbCode_RpbCounterGetReq byte = 52 59 | const rpbCode_RpbCounterGetResp byte = 53 60 | const rpbCode_RpbYokozunaIndexGetReq byte = 54 61 | const rpbCode_RpbYokozunaIndexGetResp byte = 55 62 | const rpbCode_RpbYokozunaIndexPutReq byte = 56 63 | const rpbCode_RpbYokozunaIndexDeleteReq byte = 57 64 | const rpbCode_RpbYokozunaSchemaGetReq byte = 58 65 | const rpbCode_RpbYokozunaSchemaGetResp byte = 59 66 | const rpbCode_RpbYokozunaSchemaPutReq byte = 60 67 | const rpbCode_DtFetchReq byte = 80 68 | const rpbCode_DtFetchResp byte = 81 69 | const rpbCode_DtUpdateReq byte = 82 70 | const rpbCode_DtUpdateResp byte = 83 71 | const rpbCode_TsQueryReq byte = 90 72 | const rpbCode_TsQueryResp byte = 91 73 | const rpbCode_TsPutReq byte = 92 74 | const rpbCode_TsPutResp byte = 93 75 | const rpbCode_TsDelReq byte = 94 76 | const rpbCode_TsDelResp byte = 95 77 | const rpbCode_TsGetReq byte = 96 78 | const rpbCode_TsGetResp byte = 97 79 | const rpbCode_TsListKeysReq byte = 98 80 | const rpbCode_TsListKeysResp byte = 99 81 | const rpbCode_RpbAuthReq byte = 253 82 | const rpbCode_RpbAuthResp byte = 254 83 | const rpbCode_RpbStartTls byte = 255 84 | -------------------------------------------------------------------------------- /connection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | ) 21 | 22 | func TestCreateConnection(t *testing.T) { 23 | addr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:8098") 24 | if err != nil { 25 | t.Error(err.Error()) 26 | } 27 | opts := &connectionOptions{ 28 | remoteAddress: addr, 29 | connectTimeout: tenSeconds, 30 | requestTimeout: tenSeconds, 31 | tempNetErrorRetries: 10, 32 | } 33 | var conn *connection 34 | if conn, err = newConnection(opts); err == nil { 35 | if conn.addr.Port != 8098 { 36 | t.Errorf("expected port 8098, got: %s", string(conn.addr.Port)) 37 | } 38 | if conn.addr.Zone != "" { 39 | t.Errorf("expected empty zone, got: %s", string(conn.addr.Zone)) 40 | } 41 | if !conn.addr.IP.Equal(localhost) { 42 | t.Errorf("expected %v, got: %v", localhost, conn.addr.IP) 43 | } 44 | if conn.connectTimeout != tenSeconds { 45 | t.Errorf("expected %v, got: %v", tenSeconds, conn.connectTimeout) 46 | } 47 | if conn.requestTimeout != tenSeconds { 48 | t.Errorf("expected %v, got: %v", tenSeconds, conn.requestTimeout) 49 | } 50 | if expected, actual := false, conn.inFlight; expected != actual { 51 | t.Errorf("expected %v, got: %v", expected, actual) 52 | } 53 | if got, want := conn.tempNetErrorRetries, opts.tempNetErrorRetries; got != want { 54 | t.Errorf("got %v, want %v", got, want) 55 | } 56 | } else { 57 | t.Error(err.Error()) 58 | } 59 | } 60 | 61 | func TestCreateConnectionWithBadAddress(t *testing.T) { 62 | _, err := net.ResolveTCPAddr("tcp4", "123456.89.9813948.19328419348:80983r6") 63 | if err == nil { 64 | t.Error("expected error") 65 | } 66 | } 67 | 68 | func TestCreateConnectionRequiresOptions(t *testing.T) { 69 | if _, err := newConnection(nil); err == nil { 70 | t.Error("expected error when creating Connection without options") 71 | } 72 | } 73 | 74 | func TestEnsureDefaultConnectionValues(t *testing.T) { 75 | addr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:8087") 76 | if err != nil { 77 | t.Error(err.Error()) 78 | } 79 | opts := &connectionOptions{remoteAddress: addr} 80 | var conn *connection 81 | if conn, err = newConnection(opts); err == nil { 82 | if conn.addr.Port != 8087 { 83 | t.Errorf("expected port 8087, got: %s", string(conn.addr.Port)) 84 | } 85 | if conn.addr.Zone != "" { 86 | t.Errorf("expected empty zone, got: %s", string(conn.addr.Zone)) 87 | } 88 | if !conn.addr.IP.Equal(localhost) { 89 | t.Errorf("expected %v, got: %v", localhost, conn.addr.IP) 90 | } 91 | if conn.connectTimeout != defaultConnectTimeout { 92 | t.Errorf("expected %v, got: %v", defaultConnectTimeout, conn.connectTimeout) 93 | } 94 | if conn.requestTimeout != defaultRequestTimeout { 95 | t.Errorf("expected %v, got: %v", defaultRequestTimeout, conn.requestTimeout) 96 | } 97 | if expected, actual := false, conn.inFlight; expected != actual { 98 | t.Errorf("expected %v, got: %v", expected, actual) 99 | } 100 | } else { 101 | t.Error(err.Error()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/dev/using/updates/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | 20 | riak "github.com/basho/riak-go-client" 21 | ) 22 | 23 | /* 24 | Code samples from: 25 | http://docs.basho.com/riak/latest/dev/using/updates/ 26 | 27 | make sure this bucket-type is created: 28 | siblings 29 | 30 | riak-admin bucket-type create siblings '{"props":{"allow_mult":true}}' 31 | riak-admin bucket-type activate siblings 32 | */ 33 | func main() { 34 | //riak.EnableDebugLogging = true 35 | 36 | nodeOpts := &riak.NodeOptions{ 37 | RemoteAddress: "riak-test:10017", 38 | } 39 | 40 | var node *riak.Node 41 | var err error 42 | if node, err = riak.NewNode(nodeOpts); err != nil { 43 | fmt.Println(err.Error()) 44 | } 45 | 46 | nodes := []*riak.Node{node} 47 | opts := &riak.ClusterOptions{ 48 | Nodes: nodes, 49 | } 50 | 51 | cluster, err := riak.NewCluster(opts) 52 | if err != nil { 53 | fmt.Println(err.Error()) 54 | } 55 | 56 | defer func() { 57 | if err = cluster.Stop(); err != nil { 58 | fmt.Println(err.Error()) 59 | } 60 | }() 61 | 62 | if err = cluster.Start(); err != nil { 63 | fmt.Println(err.Error()) 64 | } 65 | 66 | // ping 67 | ping := &riak.PingCommand{} 68 | if err = cluster.Execute(ping); err != nil { 69 | fmt.Println(err.Error()) 70 | } else { 71 | fmt.Println("ping passed") 72 | } 73 | 74 | storeCoach(cluster) 75 | 76 | if err = updateCoach(cluster, "seahawks", "Bob Abooey"); err != nil { 77 | fmt.Println(err.Error()) 78 | } 79 | } 80 | 81 | func storeCoach(cluster *riak.Cluster) { 82 | obj := &riak.Object{ 83 | ContentType: "text/plain", 84 | Charset: "utf-8", 85 | ContentEncoding: "utf-8", 86 | Value: []byte("Pete Carroll"), 87 | } 88 | 89 | cmd, err := riak.NewStoreValueCommandBuilder(). 90 | WithBucketType("siblings"). 91 | WithBucket("coaches"). 92 | WithKey("seahawks"). 93 | WithContent(obj). 94 | Build() 95 | 96 | if err != nil { 97 | fmt.Println(err.Error()) 98 | return 99 | } 100 | 101 | if err = cluster.Execute(cmd); err != nil { 102 | fmt.Println(err.Error()) 103 | return 104 | } 105 | 106 | fmt.Println("Stored Pete Carroll") 107 | } 108 | 109 | func updateCoach(cluster *riak.Cluster, team, newCoach string) error { 110 | var cmd riak.Command 111 | var err error 112 | 113 | cmd, err = riak.NewFetchValueCommandBuilder(). 114 | WithBucketType("siblings"). 115 | WithBucket("coaches"). 116 | WithKey(team). 117 | Build() 118 | 119 | if err != nil { 120 | return err 121 | } 122 | 123 | if err = cluster.Execute(cmd); err != nil { 124 | return err 125 | } 126 | 127 | fvc := cmd.(*riak.FetchValueCommand) 128 | obj := fvc.Response.Values[0] 129 | obj.Value = []byte(newCoach) 130 | 131 | cmd, err = riak.NewStoreValueCommandBuilder(). 132 | WithBucketType("siblings"). 133 | WithBucket("coaches"). 134 | WithKey(team). 135 | WithContent(obj). 136 | Build() 137 | 138 | if err != nil { 139 | return err 140 | } 141 | 142 | if err = cluster.Execute(cmd); err != nil { 143 | return err 144 | } 145 | 146 | return nil 147 | } 148 | -------------------------------------------------------------------------------- /connection_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "io" 21 | "net" 22 | "reflect" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | func TestSuccessfulConnection(t *testing.T) { 28 | connChan := make(chan bool) 29 | 30 | var onConn = func(c net.Conn) bool { 31 | defer c.Close() 32 | connChan <- true 33 | return true 34 | } 35 | o := &testListenerOpts{ 36 | test: t, 37 | onConn: onConn, 38 | } 39 | tl := newTestListener(o) 40 | defer tl.stop() 41 | tl.start() 42 | 43 | opts := &connectionOptions{ 44 | remoteAddress: tl.addr.(*net.TCPAddr), 45 | } 46 | 47 | conn, err := newConnection(opts) 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | if err := conn.connect(); err != nil { 53 | t.Error(err) 54 | } 55 | 56 | sawConnection := <-connChan 57 | 58 | if err := conn.close(); err != nil { 59 | t.Error(err) 60 | } 61 | 62 | if !sawConnection { 63 | t.Error("did not connect") 64 | } 65 | } 66 | 67 | func TestConnectionClosed(t *testing.T) { 68 | var onConn = func(c net.Conn) bool { 69 | if err := c.Close(); err != nil { 70 | t.Error(err) 71 | } 72 | return true 73 | } 74 | o := &testListenerOpts{ 75 | test: t, 76 | onConn: onConn, 77 | } 78 | tl := newTestListener(o) 79 | tl.start() 80 | 81 | opts := &connectionOptions{ 82 | remoteAddress: tl.addr.(*net.TCPAddr), 83 | } 84 | 85 | conn, err := newConnection(opts) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | 90 | if err := conn.connect(); err != nil { 91 | t.Error("unexpected error in connect", err) 92 | } else { 93 | tl.stop() 94 | cmd := &PingCommand{} 95 | if err := conn.execute(cmd); err != nil { 96 | if operr, ok := err.(*net.OpError); ok { 97 | t.Log("op error", operr, operr.Op) 98 | } else if err == io.EOF { 99 | t.Log("saw EOF") 100 | } else { 101 | t.Errorf("expected to see net.OpError or io.EOF, but got '%s' (type: %v)", err.Error(), reflect.TypeOf(err)) 102 | } 103 | } else { 104 | t.Error("expected error in execute") 105 | } 106 | } 107 | } 108 | 109 | func TestConnectionTimeout(t *testing.T) { 110 | addr, err := net.ResolveTCPAddr("tcp4", "10.255.255.1:65535") 111 | if err != nil { 112 | t.Error(err.Error()) 113 | } 114 | 115 | opts := &connectionOptions{ 116 | remoteAddress: addr, 117 | connectTimeout: time.Millisecond * 150, 118 | } 119 | 120 | if conn, err := newConnection(opts); err == nil { 121 | if err := conn.connect(); err == nil { 122 | t.Error("expected to see timeout error") 123 | } else { 124 | if neterr, ok := err.(net.Error); ok && neterr.Timeout() { 125 | t.Log("timeout error", neterr) 126 | } else if operr, ok := err.(*net.OpError); ok { 127 | t.Log("op error", operr) 128 | } else { 129 | t.Errorf("expected to see timeout error, but got '%s' (type: %v)", err.Error(), reflect.TypeOf(err)) 130 | } 131 | } 132 | } else { 133 | t.Error(err) 134 | } 135 | } 136 | 137 | func TestConnectionSuccess(t *testing.T) { 138 | o := &testListenerOpts{ 139 | test: t, 140 | host: "127.0.0.1", 141 | port: 1340, 142 | } 143 | tl := newTestListener(o) 144 | defer tl.stop() 145 | tl.start() 146 | 147 | addr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:1340") 148 | if err != nil { 149 | t.Error(err.Error()) 150 | } 151 | 152 | opts := &connectionOptions{ 153 | remoteAddress: addr, 154 | connectTimeout: tenSeconds, 155 | } 156 | 157 | if conn, err := newConnection(opts); err == nil { 158 | if err := conn.connect(); err != nil { 159 | t.Error("unexpected error:", err) 160 | } 161 | } else { 162 | t.Error("unexpected error:", err) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /security_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build security 16 | 17 | package riak 18 | 19 | import ( 20 | "crypto/tls" 21 | "crypto/x509" 22 | "io/ioutil" 23 | "testing" 24 | ) 25 | 26 | func buildClusterAndRunTest(t *testing.T, nodeOptions *NodeOptions) { 27 | var err error 28 | var node *Node 29 | if node, err = NewNode(nodeOptions); err != nil { 30 | t.Error(err.Error()) 31 | } 32 | if node == nil { 33 | t.FailNow() 34 | } 35 | 36 | nodes := []*Node{node} 37 | opts := &ClusterOptions{ 38 | Nodes: nodes, 39 | } 40 | 41 | if expected, actual := 1, len(opts.Nodes); expected != actual { 42 | t.Errorf("expected %v, got %v", expected, actual) 43 | } 44 | if expected, actual := node, opts.Nodes[0]; expected != actual { 45 | t.Errorf("expected %v, got %v", expected, actual) 46 | } 47 | 48 | cluster, err := NewCluster(opts) 49 | if err != nil { 50 | t.Error(err.Error()) 51 | } 52 | 53 | defer func() { 54 | if err := cluster.Stop(); err != nil { 55 | t.Error(err.Error()) 56 | } 57 | }() 58 | 59 | if expected, actual := node, cluster.nodes[0]; expected != actual { 60 | t.Errorf("expected %v, got %v", expected, actual) 61 | } 62 | 63 | if err := cluster.Start(); err != nil { 64 | t.Error(err.Error()) 65 | } 66 | 67 | command := &PingCommand{} 68 | if err := cluster.Execute(command); err != nil { 69 | t.Error(err.Error()) 70 | } 71 | 72 | if expected, actual := true, command.Success(); expected != actual { 73 | t.Errorf("expected %v, got %v", expected, actual) 74 | } 75 | } 76 | 77 | func TestExecuteCommandOnClusterWithSecurity(t *testing.T) { 78 | var err error 79 | var rootCertPemData []byte 80 | if rootCertPemData, err = ioutil.ReadFile("./tools/test-ca/certs/cacert.pem"); err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | rootCertPool := x509.NewCertPool() 84 | if ok := rootCertPool.AppendCertsFromPEM(rootCertPemData); !ok { 85 | t.Fatal("could not append PEM cert data") 86 | } 87 | tlsConfig := &tls.Config{ 88 | ServerName: "riak-test", 89 | InsecureSkipVerify: false, // set to 'true' to not require CA certs 90 | RootCAs: rootCertPool, 91 | } 92 | authOptions := &AuthOptions{ 93 | User: "riakpass", 94 | Password: "Test1234", 95 | TlsConfig: tlsConfig, 96 | } 97 | nodeOptions := &NodeOptions{ 98 | RemoteAddress: getRiakAddress(), 99 | AuthOptions: authOptions, 100 | } 101 | buildClusterAndRunTest(t, nodeOptions) 102 | } 103 | 104 | func TestExecuteCommandOnClusterWithSecurityAndClientCertificate(t *testing.T) { 105 | var err error 106 | var rootCertPemData []byte 107 | if rootCertPemData, err = ioutil.ReadFile("./tools/test-ca/certs/cacert.pem"); err != nil { 108 | t.Fatal(err.Error()) 109 | } 110 | rootCertPool := x509.NewCertPool() 111 | if ok := rootCertPool.AppendCertsFromPEM(rootCertPemData); !ok { 112 | t.Fatal("could not append PEM cert data") 113 | } 114 | 115 | var cert tls.Certificate 116 | if cert, err = tls.LoadX509KeyPair( 117 | "./tools/test-ca/certs/riakuser-client-cert.pem", 118 | "./tools/test-ca/private/riakuser-client-cert-key.pem"); err != nil { 119 | t.Fatal(err.Error()) 120 | } 121 | 122 | tlsConfig := &tls.Config{ 123 | ServerName: "riak-test", 124 | InsecureSkipVerify: false, // set to 'true' to not require CA certs 125 | RootCAs: rootCertPool, 126 | ClientCAs: rootCertPool, 127 | Certificates: []tls.Certificate{cert}, 128 | } 129 | authOptions := &AuthOptions{ 130 | User: "riakuser", 131 | TlsConfig: tlsConfig, 132 | } 133 | nodeOptions := &NodeOptions{ 134 | RemoteAddress: getRiakAddress(), 135 | AuthOptions: authOptions, 136 | } 137 | buildClusterAndRunTest(t, nodeOptions) 138 | } 139 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "strings" 21 | ) 22 | 23 | const ErrClientInvalidRemoteAddress = "[Client] invalid RemoteAddress '%s'" 24 | 25 | var ( 26 | ErrClientOptionsRequired = newClientError("[Client] options are required", nil) 27 | ErrClientMissingRequiredData = newClientError("[Client] options must specify either a Cluster or a set of RemoteAddresses", nil) 28 | ) 29 | 30 | // Client object contains your cluster object 31 | type Client struct { 32 | cluster *Cluster 33 | } 34 | 35 | // Options for creating a new Client. Either Cluster or Port/RemoteAddress information must be provided 36 | type NewClientOptions struct { 37 | Cluster *Cluster 38 | Port uint16 // NB: if specified, all connections will use this value if port is not provided 39 | RemoteAddresses []string // NB: in the form HOST|IP[:PORT] 40 | } 41 | 42 | // NewClient generates a new Client object using the provided options 43 | func NewClient(opts *NewClientOptions) (*Client, error) { 44 | if opts == nil { 45 | return nil, ErrClientOptionsRequired 46 | } 47 | if opts.Cluster != nil { 48 | return newClientUsingCluster(opts.Cluster) 49 | } 50 | if opts.RemoteAddresses != nil { 51 | return newClientUsingAddresses(opts.Port, opts.RemoteAddresses) 52 | } 53 | return nil, ErrClientMissingRequiredData 54 | } 55 | 56 | func (c *Client) Cluster() *Cluster { 57 | return c.cluster 58 | } 59 | 60 | // Execute (synchronously) the provided Command against the cluster 61 | func (c *Client) Execute(cmd Command) error { 62 | return c.cluster.Execute(cmd) 63 | } 64 | 65 | // Execute (asynchronously) the provided Command against the cluster 66 | func (c *Client) ExecuteAsync(a *Async) error { 67 | return c.cluster.ExecuteAsync(a) 68 | } 69 | 70 | // Pings the cluster 71 | func (c *Client) Ping() (bool, error) { 72 | cmd := &PingCommand{} 73 | err := c.cluster.Execute(cmd) 74 | return cmd.Success(), err 75 | } 76 | 77 | // Stop the nodes in the cluster and the cluster itself 78 | func (c *Client) Stop() error { 79 | return c.cluster.Stop() 80 | } 81 | 82 | func newClientUsingCluster(cluster *Cluster) (*Client, error) { 83 | if err := cluster.Start(); err != nil { 84 | return nil, err 85 | } 86 | return &Client{ 87 | cluster: cluster, 88 | }, nil 89 | } 90 | 91 | func newClientUsingAddresses(port uint16, remoteAddresses []string) (*Client, error) { 92 | if len(remoteAddresses) == 0 { 93 | remoteAddresses = make([]string, 1) 94 | remoteAddresses[0] = defaultRemoteAddress 95 | } 96 | nodes := make([]*Node, len(remoteAddresses)) 97 | for i, ra := range remoteAddresses { 98 | nopts := &NodeOptions{ 99 | MinConnections: 10, 100 | } 101 | s := strings.SplitN(ra, ":", 2) 102 | switch len(s) { 103 | case 0: 104 | return nil, newClientError(fmt.Sprintf(ErrClientInvalidRemoteAddress, ra), nil) 105 | case 1: 106 | if port > 0 { 107 | nopts.RemoteAddress = fmt.Sprintf("%s:%d", s[0], port) 108 | } else { 109 | nopts.RemoteAddress = fmt.Sprintf("%s:%d", s[0], defaultRemotePort) 110 | } 111 | case 2: 112 | if p, err := strconv.Atoi(s[1]); err != nil { 113 | return nil, newClientError(ErrClientInvalidRemoteAddress, err) 114 | } else { 115 | nopts.RemoteAddress = fmt.Sprintf("%s:%d", s[0], p) 116 | } 117 | default: 118 | return nil, newClientError(fmt.Sprintf(ErrClientInvalidRemoteAddress, ra), nil) 119 | } 120 | if node, err := NewNode(nopts); err != nil { 121 | return nil, err 122 | } else { 123 | nodes[i] = node 124 | } 125 | } 126 | copts := &ClusterOptions{ 127 | Nodes: nodes, 128 | } 129 | if cluster, err := NewCluster(copts); err != nil { 130 | return nil, err 131 | } else { 132 | return newClientUsingCluster(cluster) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /examples/dev/using/data-types/hyperloglog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "time" 21 | 22 | "errors" 23 | riak "github.com/basho/riak-go-client" 24 | ) 25 | 26 | /* 27 | Code samples from: 28 | http://docs.basho.com/riak/kv/latest/developing/data-types/hyperloglogs/ 29 | 30 | make sure these bucket-types are created: 31 | 32 | riak_admin bucket-type create hlls '{"props":{"datatype":"hll"}}' 33 | */ 34 | 35 | func main() { 36 | //riak.EnableDebugLogging = true 37 | 38 | nodeOpts := &riak.NodeOptions{ 39 | RemoteAddress: "riak-test:10017", 40 | RequestTimeout: time.Second * 60, 41 | } 42 | 43 | var node *riak.Node 44 | var err error 45 | if node, err = riak.NewNode(nodeOpts); err != nil { 46 | fmt.Println(err.Error()) 47 | } 48 | 49 | nodes := []*riak.Node{node} 50 | opts := &riak.ClusterOptions{ 51 | Nodes: nodes, 52 | } 53 | 54 | cluster, err := riak.NewCluster(opts) 55 | if err != nil { 56 | fmt.Println(err.Error()) 57 | } 58 | 59 | defer func() { 60 | if err = cluster.Stop(); err != nil { 61 | fmt.Println(err.Error()) 62 | } 63 | }() 64 | 65 | if err = cluster.Start(); err != nil { 66 | fmt.Println(err.Error()) 67 | } 68 | 69 | // ping 70 | ping := &riak.PingCommand{} 71 | if err = cluster.Execute(ping); err != nil { 72 | fmt.Println(err.Error()) 73 | } else { 74 | fmt.Println("ping passed") 75 | } 76 | 77 | if err = assertHyperloglogStartsEmpty(cluster); err != nil { 78 | ErrExit(err) 79 | } 80 | 81 | if err = updateHyperloglog(cluster); err != nil { 82 | ErrExit(err) 83 | } 84 | 85 | if err = fetchHyperloglog(cluster); err != nil { 86 | ErrExit(err) 87 | } 88 | } 89 | 90 | func ErrExit(err error) { 91 | os.Stderr.WriteString(err.Error()) 92 | os.Exit(1) 93 | } 94 | 95 | func assertHyperloglogStartsEmpty(cluster *riak.Cluster) error { 96 | var resp *riak.FetchHllResponse 97 | 98 | builder := riak.NewFetchHllCommandBuilder() 99 | cmd, err := builder.WithBucketType("hlls"). 100 | WithBucket("hello"). 101 | WithKey("darkness"). 102 | Build() 103 | if err != nil { 104 | return err 105 | } 106 | if err = cluster.Execute(cmd); err != nil { 107 | return err 108 | } 109 | if fc, ok := cmd.(*riak.FetchHllCommand); ok { 110 | if fc.Response == nil { 111 | return errors.New("expected non-nil Response") 112 | } 113 | resp = fc.Response 114 | } 115 | 116 | fmt.Println("Hyperloglog cardinality: ", resp.Cardinality) 117 | fmt.Println("Hyperloglog isNotFound: ", resp.IsNotFound) 118 | return nil 119 | } 120 | 121 | func updateHyperloglog(cluster *riak.Cluster) error { 122 | adds := [][]byte{ 123 | []byte("Jokes"), 124 | []byte("Are"), 125 | []byte("Better"), 126 | []byte("Explained"), 127 | []byte("Jokes"), 128 | } 129 | 130 | builder := riak.NewUpdateHllCommandBuilder() 131 | cmd, err := builder.WithBucketType("hlls"). 132 | WithBucket("hello"). 133 | WithKey("darkness"). 134 | WithAdditions(adds...). 135 | Build() 136 | if err != nil { 137 | return err 138 | } 139 | 140 | return cluster.Execute(cmd) 141 | } 142 | 143 | func fetchHyperloglog(cluster *riak.Cluster) error { 144 | var resp *riak.FetchHllResponse 145 | 146 | builder := riak.NewFetchHllCommandBuilder() 147 | cmd, err := builder.WithBucketType("hlls"). 148 | WithBucket("hello"). 149 | WithKey("darkness"). 150 | Build() 151 | if err != nil { 152 | return err 153 | } 154 | if err = cluster.Execute(cmd); err != nil { 155 | return err 156 | } 157 | if fc, ok := cmd.(*riak.FetchHllCommand); ok { 158 | if fc.Response == nil { 159 | return errors.New("expected non-nil Response") 160 | } 161 | resp = fc.Response 162 | } 163 | 164 | // We added "Jokes" twice, but, remember, the algorithm only counts the 165 | // unique elements we've added to the data structure. 166 | fmt.Println("Hyperloglog cardinality: ", resp.Cardinality) 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Riak Go Client 2 | 3 | The **Riak Go Client** is a client which makes it easy to communicate with [Riak](http://basho.com/riak/), an open source, distributed database that focuses on high availability, horizontal scalability, and *predictable* latency. Both Riak and this code is maintained by [Basho](http://www.basho.com/). 4 | 5 | The [latest version](https://github.com/basho/riak-go-client/releases/latest) of the client supports both Riak KV 2.0+, and Riak TS 1.0+. 6 | 7 | ## Build Status 8 | 9 | [![Build Status](https://travis-ci.org/basho/riak-go-client.svg?branch=master)](https://travis-ci.org/basho/riak-go-client) 10 | 11 | # Installation 12 | 13 | `go get github.com/basho/riak-go-client` 14 | 15 | # Documentation 16 | 17 | * [API documentation on Godoc](https://godoc.org/github.com/basho/riak-go-client) 18 | * [Wiki](https://github.com/basho/riak-go-client/wiki) 19 | * [Release Notes](https://github.com/basho/riak-go-client/blob/master/RELNOTES.md). 20 | 21 | # Testing / Contributing 22 | 23 | This repository's maintainers are engineers at Basho and we welcome your contribution to the project! Review the details in [CONTRIBUTING.md](CONTRIBUTING.md) in order to give back to this project. 24 | 25 | *Note:* Please clone this repository in such a manner that submodules are also cloned: 26 | 27 | ``` 28 | git clone --recursive https://github.com/basho/riak-go-client 29 | ``` 30 | 31 | OR: 32 | 33 | ``` 34 | git clone https://github.com/basho/riak-go-client 35 | git submodule init --update 36 | ``` 37 | 38 | ## Unit Tests 39 | 40 | ```sh 41 | make unit-test 42 | ``` 43 | 44 | ## Integration Tests 45 | 46 | You have two options to run Riak locally - either build from source, or use a pre-installed Riak package. 47 | 48 | ### Source 49 | 50 | To setup the default test configuration, build a Riak node from a clone of `github.com/basho/riak`: 51 | 52 | ```sh 53 | # check out latest release tag 54 | git checkout riak-2.1.4 55 | make locked-deps 56 | make rel 57 | ``` 58 | 59 | [Source build documentation](http://docs.basho.com/riak/kv/latest/setup/installing/source/). 60 | 61 | When building from source, the protocol buffers port will be `8087` and HTTP will be `8098`. 62 | 63 | ### Package 64 | 65 | Install using your platform's package manager ([docs](http://docs.basho.com/riak/kv/latest/setup/installing/)) 66 | 67 | When installing from a package, the protocol buffers port will be `8087` and HTTP will be `8098`. 68 | 69 | ### Running Integration Tests 70 | 71 | * Ensure you've initialized this repo's submodules: 72 | 73 | ```sh 74 | git submodule update --init 75 | ``` 76 | 77 | * Run the following: 78 | 79 | ```sh 80 | ./tools/setup-riak 81 | make integration-test 82 | ``` 83 | 84 | This repository's maintainers are engineers at Basho and we welcome your contribution to the project! Review the details in [CONTRIBUTING.md](CONTRIBUTING.md) in order to give back to this project. 85 | 86 | ### An honest disclaimer 87 | 88 | Due to our obsession with stability and our rich ecosystem of users, community updates on this repo may take a little longer to review. 89 | 90 | The most helpful way to contribute is by reporting your experience through issues. Issues may not be updated while we review internally, but they're still incredibly appreciated. 91 | 92 | Thank you for being part of the community! We love you for it. 93 | 94 | ## Roadmap 95 | 96 | * 1.0.0 - Full Riak 2 support with command queuing and retries. 97 | 98 | ## License 99 | 100 | The **Riak Go Client** is Open Source software released under the Apache 2.0 101 | License. Please see the [LICENSE](LICENSE) file for full license details. 102 | 103 | These excellent community projects inspired this client and parts of their code 104 | are in `riak-go-client` as well: 105 | 106 | * [`goriakpbc`](https://github.com/tpjg/goriakpbc) 107 | * [`riaken-core`](https://github.com/riaken/riaken-core) 108 | * [`backoff`](https://github.com/jpillora/backoff) 109 | 110 | ## Authors 111 | 112 | * [Luke Bakken](https://github.com/lukebakken) 113 | * [Christopher Mancini](https://github.com/christophermancini) 114 | 115 | ## Contributors 116 | 117 | Thank you to all of our contributors! 118 | 119 | * [Ian Lozinski](https://github.com/i) 120 | * [Sergio C. Arteaga](https://github.com/tegioz) 121 | * [Andrew Zeneski](https://github.com/andrewzeneski) 122 | * [Кирилл Александрович Журавлев](https://github.com/kazhuravlev) 123 | * [Paul Guelpa](https://github.com/pguelpa) 124 | * [Xabier Larrakoetxea Gallego](https://github.com/slok) 125 | * [Paul Maseberg](https://github.com/pmaseberg) 126 | * [Weerasak Chongnguluam](https://github.com/iporsut) 127 | * [Hawk Newton](https://github.com/hawknewton) 128 | * [Beau Brewer](https://github.com/beaubrewer) 129 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | "sync/atomic" 22 | "time" 23 | 24 | proto "github.com/golang/protobuf/proto" 25 | ) 26 | 27 | // Global private var used in debug mode to differentiate command names in debug output 28 | var c uint64 = 0 29 | 30 | // Interface implemented by Command types that can be re-tried 31 | type retryableCommand interface { 32 | setLastNode(*Node) 33 | getLastNode() *Node 34 | } 35 | 36 | // Implementation of retryableCommand 37 | type retryableCommandImpl struct { 38 | lastNode *Node 39 | } 40 | 41 | func (cmd *retryableCommandImpl) setLastNode(lastNode *Node) { 42 | if lastNode == nil { 43 | panic("[retryableCommandImpl] nil last node") 44 | } 45 | cmd.lastNode = lastNode 46 | } 47 | 48 | func (cmd *retryableCommandImpl) getLastNode() *Node { 49 | return cmd.lastNode 50 | } 51 | 52 | type commandImpl struct { 53 | error error 54 | success bool 55 | name string 56 | } 57 | 58 | func (cmd *commandImpl) Success() bool { 59 | return cmd.success == true 60 | } 61 | 62 | func (cmd *commandImpl) Error() error { 63 | return cmd.error 64 | } 65 | 66 | func (cmd *commandImpl) onError(err error) { 67 | cmd.success = false 68 | cmd.error = err 69 | } 70 | 71 | func (cmd *commandImpl) onRetry() { 72 | cmd.error = nil 73 | } 74 | 75 | func (cmd *commandImpl) getName(n string) string { 76 | if n == "" { 77 | panic("getName: n must not be empty") 78 | } 79 | if cmd.name == "" { 80 | if EnableDebugLogging == true { 81 | cmd.name = fmt.Sprintf("%s-%v", n, atomic.AddUint64(&c, 1)) 82 | } else { 83 | cmd.name = n 84 | } 85 | } 86 | return cmd.name 87 | } 88 | 89 | // Interface implemented by Command types that can be streamed 90 | type streamingCommand interface { 91 | isDone() bool 92 | } 93 | 94 | // Interface implemented by Command types that have a timeout 95 | type timeoutCommand interface { 96 | getTimeout() time.Duration 97 | } 98 | 99 | type timeoutImpl struct { 100 | timeout time.Duration 101 | } 102 | 103 | func (cmd *timeoutImpl) getTimeout() time.Duration { 104 | return cmd.timeout 105 | } 106 | 107 | // Interface implemented by Commands that list data from Riak 108 | type listingCommand interface { 109 | getAllowListing() bool 110 | } 111 | 112 | type listingImpl struct { 113 | allowListing bool 114 | } 115 | 116 | func (cmd *listingImpl) getAllowListing() bool { 117 | return cmd.allowListing 118 | } 119 | 120 | // CommandBuilder interface requires Build() method for generating the Command 121 | // to be executed 122 | type CommandBuilder interface { 123 | Build() (Command, error) 124 | } 125 | 126 | // Command interface enforces proper structure of a Command object 127 | type Command interface { 128 | Name() string 129 | Success() bool 130 | Error() error 131 | getRequestCode() byte 132 | constructPbRequest() (proto.Message, error) 133 | onRetry() 134 | onError(error) 135 | onSuccess(proto.Message) error // NB: important for streaming commands to "do the right thing" here 136 | getResponseCode() byte 137 | getResponseProtobufMessage() proto.Message 138 | } 139 | 140 | func getRiakMessage(cmd Command) (msg []byte, err error) { 141 | requestCode := cmd.getRequestCode() 142 | if requestCode == 0 { 143 | panic(fmt.Sprintf("Must have non-zero value for getRequestCode(): %s", cmd.Name())) 144 | } 145 | 146 | var rpb proto.Message 147 | rpb, err = cmd.constructPbRequest() 148 | if err != nil { 149 | return 150 | } 151 | 152 | var bytes []byte 153 | if rpb != nil { 154 | bytes, err = proto.Marshal(rpb) 155 | if err != nil { 156 | return nil, err 157 | } 158 | } 159 | 160 | msg = buildRiakMessage(requestCode, bytes) 161 | return 162 | } 163 | 164 | func decodeRiakMessage(cmd Command, data []byte) (msg proto.Message, err error) { 165 | responseCode := cmd.getResponseCode() 166 | if responseCode == 0 { 167 | panic(fmt.Sprintf("Must have non-zero value for getResponseCode(): %s", cmd.Name())) 168 | } 169 | 170 | err = rpbValidateResp(data, responseCode) 171 | if err != nil { 172 | return 173 | } 174 | 175 | if len(data) > 1 { 176 | msg = cmd.getResponseProtobufMessage() 177 | if msg != nil { 178 | err = proto.Unmarshal(data[1:], msg) 179 | } 180 | } 181 | 182 | return 183 | } 184 | 185 | func buildRiakMessage(code byte, data []byte) []byte { 186 | buf := new(bytes.Buffer) 187 | // write total message length, including one byte for msg code 188 | binary.Write(buf, binary.BigEndian, uint32(len(data)+1)) 189 | // write the message code 190 | binary.Write(buf, binary.BigEndian, byte(code)) 191 | // write the protobuf data 192 | buf.Write(data) 193 | return buf.Bytes() 194 | } 195 | -------------------------------------------------------------------------------- /misc_commands_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | // Update and Reset bucket properties 24 | 25 | func TestSetAndResetBucketProperties(t *testing.T) { 26 | cluster := integrationTestsBuildCluster() 27 | defer func() { 28 | if err := cluster.Stop(); err != nil { 29 | t.Error(err.Error()) 30 | } 31 | }() 32 | 33 | const bucket = "set-reset-bucket-props" 34 | orig_nval := uint32(3) 35 | new_nval := uint32(9) 36 | 37 | var err error 38 | var cmd Command 39 | 40 | cmd, err = NewStoreBucketPropsCommandBuilder(). 41 | WithBucket(bucket). 42 | WithNVal(new_nval). 43 | Build() 44 | if err != nil { 45 | t.Fatal(err.Error()) 46 | } 47 | if err = cluster.Execute(cmd); err != nil { 48 | t.Fatal(err.Error()) 49 | } 50 | if sc, ok := cmd.(*StoreBucketPropsCommand); ok { 51 | if got, want := sc.Success(), true; got != want { 52 | t.Errorf("got %v, want %v", got, want) 53 | } 54 | } else { 55 | t.FailNow() 56 | } 57 | 58 | cmd, err = NewFetchBucketPropsCommandBuilder(). 59 | WithBucket(bucket). 60 | Build() 61 | if err != nil { 62 | t.Fatal(err.Error()) 63 | } 64 | if err = cluster.Execute(cmd); err != nil { 65 | t.Fatal(err.Error()) 66 | } 67 | if fc, ok := cmd.(*FetchBucketPropsCommand); ok { 68 | if got, want := fc.Success(), true; got != want { 69 | t.Errorf("got %v, want %v", got, want) 70 | } 71 | if got, want := fc.Response.NVal, new_nval; got != want { 72 | t.Errorf("got %v, want %v", got, want) 73 | } 74 | } else { 75 | t.FailNow() 76 | } 77 | 78 | cmd, err = NewResetBucketCommandBuilder(). 79 | WithBucket(bucket). 80 | Build() 81 | if err != nil { 82 | t.Fatal(err.Error()) 83 | } 84 | if err = cluster.Execute(cmd); err != nil { 85 | t.Fatal(err.Error()) 86 | } 87 | if rc, ok := cmd.(*ResetBucketCommand); ok { 88 | if got, want := rc.Success(), true; got != want { 89 | t.Errorf("got %v, want %v", got, want) 90 | } 91 | } else { 92 | t.FailNow() 93 | } 94 | 95 | cmd, err = NewFetchBucketPropsCommandBuilder(). 96 | WithBucket(bucket). 97 | Build() 98 | if err != nil { 99 | t.Fatal(err.Error()) 100 | } 101 | if err = cluster.Execute(cmd); err != nil { 102 | t.Fatal(err.Error()) 103 | } 104 | if fc, ok := cmd.(*FetchBucketPropsCommand); ok { 105 | if got, want := fc.Success(), true; got != want { 106 | t.Errorf("got %v, want %v", got, want) 107 | } 108 | if got, want := fc.Response.NVal, orig_nval; got != want { 109 | t.Errorf("got %v, want %v", got, want) 110 | } 111 | } else { 112 | t.FailNow() 113 | } 114 | } 115 | 116 | // Update and Reset bucket type properties 117 | 118 | func TestSetAndResetBucketTypeProperties(t *testing.T) { 119 | cluster := integrationTestsBuildCluster() 120 | defer func() { 121 | if err := cluster.Stop(); err != nil { 122 | t.Error(err.Error()) 123 | } 124 | }() 125 | 126 | const bucketType = "plain" 127 | orig_nval := uint32(3) 128 | new_nval := uint32(4) 129 | 130 | var err error 131 | var cmd Command 132 | 133 | cmd, err = NewStoreBucketTypePropsCommandBuilder(). 134 | WithBucketType(bucketType). 135 | WithNVal(new_nval). 136 | Build() 137 | if err != nil { 138 | t.Fatal(err.Error()) 139 | } 140 | if err = cluster.Execute(cmd); err != nil { 141 | t.Fatal(err.Error()) 142 | } 143 | if sc, ok := cmd.(*StoreBucketTypePropsCommand); ok { 144 | if got, want := sc.Success(), true; got != want { 145 | t.Errorf("got %v, want %v", got, want) 146 | } 147 | } else { 148 | t.FailNow() 149 | } 150 | 151 | cmd, err = NewFetchBucketTypePropsCommandBuilder(). 152 | WithBucketType(bucketType). 153 | Build() 154 | if err != nil { 155 | t.Fatal(err.Error()) 156 | } 157 | if err = cluster.Execute(cmd); err != nil { 158 | t.Fatal(err.Error()) 159 | } 160 | if fc, ok := cmd.(*FetchBucketTypePropsCommand); ok { 161 | if got, want := fc.Success(), true; got != want { 162 | t.Errorf("got %v, want %v", got, want) 163 | } 164 | if got, want := fc.Response.NVal, new_nval; got != want { 165 | t.Fatalf("got %v, want %v", got, want) 166 | } 167 | } else { 168 | t.FailNow() 169 | } 170 | 171 | cmd, err = NewStoreBucketTypePropsCommandBuilder(). 172 | WithBucketType(bucketType). 173 | WithNVal(orig_nval). 174 | Build() 175 | if err != nil { 176 | t.Fatal(err.Error()) 177 | } 178 | if err = cluster.Execute(cmd); err != nil { 179 | t.Fatal(err.Error()) 180 | } 181 | if rc, ok := cmd.(*StoreBucketTypePropsCommand); ok { 182 | if got, want := rc.Success(), true; got != want { 183 | t.Errorf("got %v, want %v", got, want) 184 | } 185 | } else { 186 | t.FailNow() 187 | } 188 | 189 | cmd, err = NewFetchBucketTypePropsCommandBuilder(). 190 | WithBucketType(bucketType). 191 | Build() 192 | if err != nil { 193 | t.Fatal(err.Error()) 194 | } 195 | if err = cluster.Execute(cmd); err != nil { 196 | t.Fatal(err.Error()) 197 | } 198 | if fc, ok := cmd.(*FetchBucketTypePropsCommand); ok { 199 | if got, want := fc.Success(), true; got != want { 200 | t.Errorf("got %v, want %v", got, want) 201 | } 202 | if got, want := fc.Response.NVal, orig_nval; got != want { 203 | t.Errorf("got %v, want %v", got, want) 204 | } 205 | } else { 206 | t.FailNow() 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /examples/cm-client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bufio" 19 | crand "crypto/rand" 20 | "encoding/hex" 21 | "errors" 22 | "fmt" 23 | "math/rand" 24 | "os" 25 | "strconv" 26 | "time" 27 | 28 | riak "github.com/basho/riak-go-client" 29 | util "github.com/lukebakken/goutil" 30 | ) 31 | 32 | var stopping bool = false 33 | var nodeCount uint16 = 5 34 | var minConnections uint16 = 10 35 | var maxConnections uint16 = 256 36 | 37 | var key int = 1 38 | var data []byte 39 | 40 | var fetchDataInterval time.Duration = time.Millisecond * 100 41 | var storeDataInterval time.Duration = time.Millisecond * 100 42 | 43 | func init() { 44 | c := 256 45 | b := make([]byte, c) 46 | _, err := crand.Read(b) 47 | if err != nil { 48 | util.ErrExit(err) 49 | } 50 | data = []byte(hex.EncodeToString(b)) // NB: bytes of utf-8 encoding 51 | 52 | rand.Seed(time.Now().Unix()) 53 | } 54 | 55 | func main() { 56 | riak.EnableDebugLogging = true 57 | 58 | sc := make(chan struct{}) 59 | 60 | port := 10017 61 | nodes := make([]*riak.Node, nodeCount) 62 | for i := uint16(0); i < nodeCount; i++ { 63 | addr := fmt.Sprintf("riak-test:%d", port) 64 | nodeOpts := &riak.NodeOptions{ 65 | MinConnections: minConnections, 66 | MaxConnections: maxConnections, 67 | RemoteAddress: addr, 68 | } 69 | if node, nerr := riak.NewNode(nodeOpts); nerr != nil { 70 | util.ErrExit(nerr) 71 | } else { 72 | if node == nil { 73 | util.ErrExit(errors.New("node is nil")) 74 | } 75 | util.LogDebug("[cm-client]", "node: %v", node) 76 | nodes[i] = node 77 | } 78 | port += 10 79 | } 80 | 81 | opts := &riak.ClusterOptions{ 82 | Nodes: nodes, 83 | ExecutionAttempts: 3, 84 | } 85 | 86 | c, err := riak.NewCluster(opts) 87 | if err != nil { 88 | util.ErrExit(err) 89 | } 90 | 91 | if serr := c.Start(); serr != nil { 92 | util.ErrExit(serr) 93 | } 94 | 95 | // ping 96 | ping := &riak.PingCommand{} 97 | if perr := c.Execute(ping); perr != nil { 98 | util.ErrExit(perr) 99 | } else { 100 | fmt.Println("ping passed") 101 | } 102 | 103 | sdc := make(chan riak.Command, minConnections) 104 | fdc := make(chan riak.Command, minConnections) 105 | 106 | defer func() { 107 | stopping = true 108 | close(sc) 109 | if serr := c.Stop(); serr != nil { 110 | util.ErrExit(serr) 111 | } 112 | close(sdc) 113 | close(fdc) 114 | }() 115 | 116 | go storeData(c, sc, sdc) 117 | go fetchData(c, sc, fdc) 118 | 119 | util.LogInfo("[cm-client]", "HIT ANY KEY TO STOP") 120 | bio := bufio.NewReader(os.Stdin) 121 | _, _, rerr := bio.ReadLine() 122 | if rerr != nil { 123 | util.LogErr("[GH-47]", rerr) 124 | } 125 | } 126 | 127 | func fetchData(c *riak.Cluster, sc chan struct{}, dc chan riak.Command) { 128 | tck := time.NewTicker(fetchDataInterval) 129 | defer func() { 130 | tck.Stop() 131 | }() 132 | 133 | util.LogDebug("[cm-client/FetchData]", "Starting worker process") 134 | defer util.LogDebug("[cm-client/FetchData]", "Stopped worker process") 135 | 136 | for !stopping { 137 | select { 138 | case <-sc: 139 | util.LogDebug("[cm-client/FetchData]", "Stopping worker process") 140 | stopping = true 141 | break 142 | case cmd := <-dc: 143 | util.LogDebug("[cm-client/FetchData]", "%v completed", cmd.Name()) 144 | case <-tck.C: 145 | for i := uint16(0); i < (minConnections * nodeCount); i++ { 146 | if stopping { 147 | break 148 | } 149 | rkey := rand.Intn(key) 150 | svc, err := riak.NewFetchValueCommandBuilder(). 151 | WithBucket("chaos-monkey"). 152 | WithKey(strconv.Itoa(rkey)). 153 | Build() 154 | if err != nil { 155 | util.ErrExit(err) 156 | } 157 | a := &riak.Async{ 158 | Command: svc, 159 | Done: dc, 160 | } 161 | if err = c.ExecuteAsync(a); err != nil { 162 | util.LogErr("[cm-client/FetchData]", err) 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | func storeData(c *riak.Cluster, sc chan struct{}, dc chan riak.Command) { 170 | tck := time.NewTicker(storeDataInterval) 171 | defer func() { 172 | tck.Stop() 173 | }() 174 | 175 | util.LogDebug("[cm-client/StoreData]", "Starting worker process") 176 | defer util.LogDebug("[cm-client/StoreData]", "Stopped worker process") 177 | 178 | obj := &riak.Object{ 179 | ContentType: "text/plain", 180 | Charset: "utf-8", 181 | ContentEncoding: "utf-8", 182 | Value: data, 183 | } 184 | 185 | for !stopping { 186 | select { 187 | case <-sc: 188 | util.LogDebug("[cm-client/StoreData]", "Stopping worker process") 189 | stopping = true 190 | break 191 | case cmd := <-dc: 192 | util.LogDebug("[cm-client/StoreData]", "%v completed", cmd.Name()) 193 | case <-tck.C: 194 | for i := uint16(0); i < (minConnections * nodeCount); i++ { 195 | if stopping { 196 | break 197 | } 198 | svc, err := riak.NewStoreValueCommandBuilder(). 199 | WithBucket("chaos-monkey"). 200 | WithKey(strconv.Itoa(key)). 201 | WithContent(obj). 202 | Build() 203 | if err != nil { 204 | util.ErrExit(err) 205 | } 206 | a := &riak.Async{ 207 | Command: svc, 208 | Done: dc, 209 | } 210 | if err = c.ExecuteAsync(a); err != nil { 211 | util.LogErr("[cm-client/StoreData]", err) 212 | } 213 | key++ 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /examples/dev/using/conflict-resolution/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | 20 | riak "github.com/basho/riak-go-client" 21 | ) 22 | 23 | /* 24 | Code samples from: 25 | http://docs.basho.com/riak/latest/dev/using/conflict-resolution/golang/ 26 | 27 | make sure this bucket-type is created: 28 | siblings 29 | 30 | riak-admin bucket-type create siblings '{"props":{"allow_mult":true}}' 31 | riak-admin bucket-type activate siblings 32 | */ 33 | 34 | func main() { 35 | //riak.EnableDebugLogging = true 36 | 37 | nodeOpts := &riak.NodeOptions{ 38 | RemoteAddress: "riak-test:10017", 39 | } 40 | 41 | var node *riak.Node 42 | var err error 43 | if node, err = riak.NewNode(nodeOpts); err != nil { 44 | fmt.Println(err.Error()) 45 | } 46 | 47 | nodes := []*riak.Node{node} 48 | opts := &riak.ClusterOptions{ 49 | Nodes: nodes, 50 | } 51 | 52 | cluster, err := riak.NewCluster(opts) 53 | if err != nil { 54 | fmt.Println(err.Error()) 55 | } 56 | 57 | defer func() { 58 | if err = cluster.Stop(); err != nil { 59 | fmt.Println(err.Error()) 60 | } 61 | }() 62 | 63 | if err = cluster.Start(); err != nil { 64 | fmt.Println(err.Error()) 65 | } 66 | 67 | // ping 68 | ping := &riak.PingCommand{} 69 | if err = cluster.Execute(ping); err != nil { 70 | fmt.Println(err.Error()) 71 | } else { 72 | fmt.Println("ping passed") 73 | } 74 | 75 | obj := &riak.Object{ 76 | ContentType: "text/plain", 77 | Charset: "utf-8", 78 | ContentEncoding: "utf-8", 79 | Value: []byte("Ren"), 80 | } 81 | 82 | storeObject(cluster, obj) 83 | storeObject(cluster, obj) 84 | readSiblings(cluster) 85 | 86 | resolveViaOverwrite(cluster) 87 | readSiblings(cluster) 88 | 89 | storeObject(cluster, obj) 90 | storeObject(cluster, obj) 91 | readSiblings(cluster) 92 | 93 | resolveChoosingFirst(cluster) 94 | readSiblings(cluster) 95 | 96 | storeObject(cluster, obj) 97 | storeObject(cluster, obj) 98 | readSiblings(cluster) 99 | 100 | resolveUsingResolver(cluster) 101 | readSiblings(cluster) 102 | } 103 | 104 | func storeObject(cluster *riak.Cluster, obj *riak.Object) error { 105 | cmd, err := riak.NewStoreValueCommandBuilder(). 106 | WithBucketType("siblings"). 107 | WithBucket("nickelodeon"). 108 | WithKey("best_character"). 109 | WithContent(obj). 110 | Build() 111 | if err != nil { 112 | return err 113 | } 114 | 115 | return cluster.Execute(cmd) 116 | } 117 | 118 | func readSiblings(cluster *riak.Cluster) error { 119 | cmd, err := riak.NewFetchValueCommandBuilder(). 120 | WithBucketType("siblings"). 121 | WithBucket("nickelodeon"). 122 | WithKey("best_character"). 123 | Build() 124 | if err != nil { 125 | return err 126 | } 127 | 128 | err = cluster.Execute(cmd) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | fcmd := cmd.(*riak.FetchValueCommand) 134 | fmt.Printf("[DevUsingConflictRes] nickelodeon/best_character has '%v' siblings\n", len(fcmd.Response.Values)) 135 | 136 | return nil 137 | } 138 | 139 | func resolveViaOverwrite(cluster *riak.Cluster) error { 140 | cmd, err := riak.NewFetchValueCommandBuilder(). 141 | WithBucketType("siblings"). 142 | WithBucket("nickelodeon"). 143 | WithKey("best_character"). 144 | Build() 145 | if err != nil { 146 | return err 147 | } 148 | 149 | err = cluster.Execute(cmd) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | fcmd := cmd.(*riak.FetchValueCommand) 155 | obj := fcmd.Response.Values[0] 156 | // This overwrites the value and provides the canonical one 157 | obj.Value = []byte("Stimpy") 158 | 159 | return storeObject(cluster, obj) 160 | } 161 | 162 | func resolveChoosingFirst(cluster *riak.Cluster) error { 163 | cmd, err := riak.NewFetchValueCommandBuilder(). 164 | WithBucketType("siblings"). 165 | WithBucket("nickelodeon"). 166 | WithKey("best_character"). 167 | Build() 168 | if err != nil { 169 | return err 170 | } 171 | 172 | err = cluster.Execute(cmd) 173 | if err != nil { 174 | return err 175 | } 176 | 177 | fcmd := cmd.(*riak.FetchValueCommand) 178 | obj := fcmd.Response.Values[0] 179 | 180 | return storeObject(cluster, obj) 181 | } 182 | 183 | type FirstSiblingResolver struct { 184 | } 185 | 186 | func (cr *FirstSiblingResolver) Resolve(objs []*riak.Object) []*riak.Object { 187 | // return the first one 188 | return []*riak.Object{ 189 | objs[0], 190 | } 191 | } 192 | 193 | func resolveUsingResolver(cluster *riak.Cluster) error { 194 | // Note: a more sophisticated resolver would 195 | // look into the objects to pick one, or perhaps 196 | // present the list to a user to choose 197 | cr := &FirstSiblingResolver{} 198 | 199 | cmd, err := riak.NewFetchValueCommandBuilder(). 200 | WithBucketType("siblings"). 201 | WithBucket("nickelodeon"). 202 | WithKey("best_character"). 203 | WithConflictResolver(cr). 204 | Build() 205 | if err != nil { 206 | return err 207 | } 208 | 209 | err = cluster.Execute(cmd) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | fcmd := cmd.(*riak.FetchValueCommand) 215 | 216 | // Test that the resolver just returned one riak.Object 217 | vlen := len(fcmd.Response.Values) 218 | if vlen != 1 { 219 | return fmt.Errorf("expected 1 object, got %v", vlen) 220 | } 221 | 222 | obj := fcmd.Response.Values[0] 223 | return storeObject(cluster, obj) 224 | } 225 | -------------------------------------------------------------------------------- /make.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Powershell script to build Riak Go Client on Windows 4 | .DESCRIPTION 5 | This script ensures that your build environment is sane and will run 'go' correctly depending on parameters passed to this script. 6 | .PARAMETER Target 7 | Target to build. Can be one of the following: 8 | * ProtoGen - generate *.pb.go files from *.proto files. 9 | Requires Go protoc utility (https://github.com/golang/protobuf) 10 | * Format - run *.go files through 'go fmt' 11 | * Test - Run 'go vet' and all tests (default target) 12 | * UnitTest - Run unit tests 13 | * IntegrationTest - Run live integration tests 14 | * IntegrationTestHll - Run Hyperloglog integration tests 15 | * TimeseriesTest - Run live timeseries tests 16 | .PARAMETER Verbose 17 | Use to increase verbosity. 18 | .EXAMPLE 19 | C:\Users\Bashoman> cd go\src\github.com\basho\riak-go-client 20 | C:\Users\Bashoman\go\src\github.com\basho\riak-go-client>.\make.ps1 -Target Protoc -Verbose 21 | .NOTES 22 | Author: Luke Bakken 23 | Date: June 1, 2015 24 | #> 25 | [CmdletBinding()] 26 | Param( 27 | [Parameter(Mandatory=$False, Position=0)] 28 | [ValidateSet('ProtoGen', 'Format', 29 | 'Test', 'UnitTest', 'IntegrationTest', 'IntegrationTestHll', 'TimeseriesTest', 30 | IgnoreCase = $True)] 31 | [string]$Target = 'Test' 32 | ) 33 | 34 | Set-StrictMode -Version Latest 35 | 36 | $package = 'github.com/basho/riak-go-client' 37 | 38 | $IsDebug = $DebugPreference -ne 'SilentlyContinue' 39 | $IsVerbose = $VerbosePreference -ne 'SilentlyContinue' 40 | 41 | # Note: 42 | # Set to Continue to see DEBUG messages 43 | if ($IsVerbose) { 44 | $DebugPreference = 'Continue' 45 | } 46 | 47 | trap 48 | { 49 | Write-Error -ErrorRecord $_ 50 | exit 1 51 | } 52 | 53 | function Get-ScriptPath { 54 | $scriptDir = Get-Variable PSScriptRoot -ErrorAction SilentlyContinue | ForEach-Object { $_.Value } 55 | if (!$scriptDir) { 56 | if ($MyInvocation.MyCommand.Path) { 57 | $scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent 58 | } 59 | } 60 | if (!$scriptDir) { 61 | if ($ExecutionContext.SessionState.Module.Path) { 62 | $scriptDir = Split-Path (Split-Path $ExecutionContext.SessionState.Module.Path) 63 | } 64 | } 65 | if (!$scriptDir) { 66 | $scriptDir = $PWD 67 | } 68 | return $scriptDir 69 | } 70 | 71 | function Do-ProtoGen { 72 | $script_path = Get-ScriptPath 73 | $rpb_path = Join-Path -Path $script_path -ChildPath 'rpb' 74 | $proto_path = Join-Path -Path $script_path -ChildPath (Join-Path -Path 'riak_pb' -ChildPath 'src') 75 | $proto_wild = Join-Path -Path $proto_path -ChildPath '*.proto' 76 | Get-ChildItem $proto_wild | ForEach-Object { 77 | $proto_file_basename = $_.BaseName 78 | $rpb_path_tmp = Join-Path -Path $rpb_path -ChildPath $proto_file_basename 79 | If (!(Test-Path $rpb_path_tmp)) { 80 | New-Item $rpb_path_tmp -Type Directory -Force 81 | } 82 | Write-Verbose "protoc: --go_out=$rpb_path_tmp --proto_path=$proto_path $_" 83 | & { protoc --go_out=$rpb_path_tmp --proto_path=$proto_path $_ } 84 | if ($? -ne $True) { 85 | throw "protoc.exe failed: $LastExitCode" 86 | } 87 | 88 | $rpb_file = Join-Path -Path $rpb_path_tmp -ChildPath "$proto_file_basename.pb.go" 89 | Write-Verbose "post-processing $rpb_file" 90 | 91 | (Get-Content $rpb_file) | 92 | ForEach-Object { 93 | $_ -Replace 'import proto "code.google.com/p/goprotobuf/proto"', 'import proto "github.com/golang/protobuf/proto"' 94 | } | Set-Content $rpb_file 95 | 96 | if ($_.Name -eq 'riak_search.proto' -or $_.Name -eq 'riak_kv.proto') { 97 | (Get-Content $rpb_file) | 98 | ForEach-Object { 99 | $_ -Replace 'import riak "riak.pb"', 'import riak "github.com/basho/riak-go-client/rpb/riak"' 100 | } | Set-Content $rpb_file 101 | } 102 | } 103 | } 104 | 105 | function Execute($cmd, $argz) { 106 | Write-Verbose "$cmd $argz" 107 | & $cmd $argz 108 | if ($? -ne $True) { 109 | throw "'$cmd $argz' failed: $LastExitCode" 110 | } 111 | Write-Debug "'$cmd $argz' exit code: $LastExitCode" 112 | } 113 | 114 | function Do-Format { 115 | $script_path = Get-ScriptPath 116 | Write-Verbose "go fmt $script_path" 117 | $cmd = 'gofmt' 118 | $argz = '-s', '-w', $script_path 119 | Execute $cmd $argz 120 | } 121 | 122 | function Do-Vet { 123 | $cmd = 'go.exe' 124 | $script_path = Get-ScriptPath 125 | $argz = 'tool', 'vet', '-shadow=true', '-shadowstrict=true', $script_path 126 | Execute $cmd $argz 127 | $argz = 'vet', $package 128 | Execute $cmd $argz 129 | } 130 | 131 | function Do-UnitTest { 132 | $v = '' 133 | if ($IsVerbose) { 134 | $v = '-v' 135 | } 136 | $cmd = 'go.exe' 137 | $argz = 'test', $v, "$package/..." 138 | Execute $cmd $argz 139 | } 140 | 141 | function Do-IntegrationTest { 142 | $v = '' 143 | if ($IsVerbose) { 144 | $v = '-v' 145 | } 146 | $cmd = 'go.exe' 147 | $argz = 'test', $v, '-tags=integration', "$package/..." 148 | Execute $cmd $argz 149 | } 150 | 151 | function Do-IntegrationTestHll { 152 | $v = '' 153 | if ($IsVerbose) { 154 | $v = '-v' 155 | } 156 | $cmd = 'go.exe' 157 | $argz = 'test', $v, '-tags=integration_hll', "$package/..." 158 | Execute $cmd $argz 159 | } 160 | 161 | function Do-TimeseriesTest { 162 | $v = '' 163 | if ($IsVerbose) { 164 | $v = '-v' 165 | } 166 | $cmd = 'go.exe' 167 | $argz = 'test', $v, '-tags=timeseries', "$package/..." 168 | Execute $cmd $argz 169 | } 170 | 171 | Write-Debug "Target: $Target" 172 | 173 | switch ($Target) 174 | { 175 | 'ProtoGen' { Do-ProtoGen } 176 | 'Format' { Do-Format } 177 | 'Test' { Do-Vet; Do-IntegrationTest } 178 | 'UnitTest' { Do-Vet; Do-UnitTest } 179 | 'IntegrationTest' { Do-Vet; Do-IntegrationTest } 180 | 'IntegrationTestHll' { Do-Vet; Do-IntegrationTestHll } 181 | 'TimeseriesTest' { Do-Vet; Do-TimeseriesTest } 182 | default { throw "Unknown target: $Target" } 183 | } 184 | 185 | exit 0 186 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "time" 20 | 21 | rpbRiak "github.com/basho/riak-go-client/rpb/riak" 22 | rpbRiakKV "github.com/basho/riak-go-client/rpb/riak_kv" 23 | ) 24 | 25 | // Link is used to represent a Riak KV object link, which is a one way link to another object within 26 | // Riak 27 | type Link struct { 28 | Bucket string 29 | Key string 30 | Tag string 31 | } 32 | 33 | // Pair is used to store user defined meta data with a key and value 34 | type Pair struct { 35 | Key string 36 | Value string 37 | } 38 | 39 | // Object structure used for representing a KV Riak object 40 | type Object struct { 41 | BucketType string 42 | Bucket string 43 | Key string 44 | IsTombstone bool 45 | Value []byte 46 | ContentType string 47 | Charset string 48 | ContentEncoding string 49 | VTag string 50 | LastModified time.Time 51 | UserMeta []*Pair 52 | Indexes map[string][]string 53 | Links []*Link 54 | VClock []byte 55 | } 56 | 57 | // HasIndexes is a bool check to determine if the object contains any secondary indexes for searching 58 | func (o *Object) HasIndexes() bool { 59 | return len(o.Indexes) > 0 60 | } 61 | 62 | // HasUserMeta is a bool check to determine if the object contains any user defined meta data 63 | func (o *Object) HasUserMeta() bool { 64 | return len(o.UserMeta) > 0 65 | } 66 | 67 | // HasLinks is a bool check to determine if the object contains any links 68 | func (o *Object) HasLinks() bool { 69 | return len(o.Links) > 0 70 | } 71 | 72 | // AddToIntIndex adds the object to the specified secondary index with the integer value to be used 73 | // for index searches 74 | func (o *Object) AddToIntIndex(indexName string, indexValue int) { 75 | o.AddToIndex(indexName, fmt.Sprintf("%v", indexValue)) 76 | } 77 | 78 | // AddToIndex adds the object to the specified secondary index with the string value to be used for 79 | // index searches 80 | func (o *Object) AddToIndex(indexName string, indexValue string) { 81 | if o.Indexes == nil { 82 | o.Indexes = make(map[string][]string) 83 | } 84 | if o.Indexes[indexName] == nil { 85 | o.Indexes[indexName] = make([]string, 1) 86 | o.Indexes[indexName][0] = indexValue 87 | } else { 88 | o.Indexes[indexName] = append(o.Indexes[indexName], indexValue) 89 | } 90 | } 91 | 92 | func fromRpbContent(rpbContent *rpbRiakKV.RpbContent) (ro *Object, err error) { 93 | // NB: ro = "Riak Object" 94 | ro = &Object{ 95 | IsTombstone: rpbContent.GetDeleted(), 96 | } 97 | 98 | if ro.IsTombstone { 99 | ro.Value = nil 100 | } else { 101 | ro.Value = rpbContent.GetValue() 102 | } 103 | 104 | ro.ContentType = string(rpbContent.GetContentType()) 105 | ro.Charset = string(rpbContent.GetCharset()) 106 | ro.ContentEncoding = string(rpbContent.GetContentEncoding()) 107 | ro.VTag = string(rpbContent.GetVtag()) 108 | ro.LastModified = time.Unix(int64(rpbContent.GetLastMod()), int64(rpbContent.GetLastModUsecs())) 109 | 110 | rpbUserMeta := rpbContent.GetUsermeta() 111 | if len(rpbUserMeta) > 0 { 112 | ro.UserMeta = make([]*Pair, len(rpbUserMeta)) 113 | for i, userMeta := range rpbUserMeta { 114 | ro.UserMeta[i] = &Pair{ 115 | Key: string(userMeta.Key), 116 | Value: string(userMeta.Value), 117 | } 118 | } 119 | } 120 | 121 | rpbIndexes := rpbContent.GetIndexes() 122 | if len(rpbIndexes) > 0 { 123 | ro.Indexes = make(map[string][]string) 124 | for _, index := range rpbIndexes { 125 | indexName := string(index.Key) 126 | indexValue := string(index.Value) 127 | if ro.Indexes[indexName] == nil { 128 | ro.Indexes[indexName] = make([]string, 1) 129 | ro.Indexes[indexName][0] = indexValue 130 | } else { 131 | ro.Indexes[indexName] = append(ro.Indexes[indexName], indexValue) 132 | } 133 | } 134 | } 135 | 136 | rpbLinks := rpbContent.GetLinks() 137 | if len(rpbLinks) > 0 { 138 | ro.Links = make([]*Link, len(rpbLinks)) 139 | for i, link := range rpbLinks { 140 | ro.Links[i] = &Link{ 141 | Bucket: string(link.Bucket), 142 | Key: string(link.Key), 143 | Tag: string(link.Tag), 144 | } 145 | } 146 | } 147 | 148 | return 149 | } 150 | 151 | func toRpbContent(ro *Object) (*rpbRiakKV.RpbContent, error) { 152 | rpbContent := &rpbRiakKV.RpbContent{ 153 | Value: ro.Value, 154 | ContentType: rpbBytes(ro.ContentType), 155 | Charset: rpbBytes(ro.Charset), 156 | ContentEncoding: rpbBytes(ro.ContentEncoding), 157 | } 158 | 159 | if ro.HasIndexes() { 160 | count := 0 161 | for _, idxValues := range ro.Indexes { 162 | count += len(idxValues) 163 | } 164 | idx := 0 165 | rpbIndexes := make([]*rpbRiak.RpbPair, count) 166 | for idxName, idxValues := range ro.Indexes { 167 | idxNameBytes := []byte(idxName) 168 | for _, idxVal := range idxValues { 169 | pair := &rpbRiak.RpbPair{ 170 | Key: idxNameBytes, 171 | Value: []byte(idxVal), 172 | } 173 | rpbIndexes[idx] = pair 174 | idx++ 175 | } 176 | } 177 | rpbContent.Indexes = rpbIndexes 178 | } 179 | 180 | if ro.HasUserMeta() { 181 | rpbUserMeta := make([]*rpbRiak.RpbPair, len(ro.UserMeta)) 182 | for i, userMeta := range ro.UserMeta { 183 | rpbUserMeta[i] = &rpbRiak.RpbPair{ 184 | Key: []byte(userMeta.Key), 185 | Value: []byte(userMeta.Value), 186 | } 187 | } 188 | rpbContent.Usermeta = rpbUserMeta 189 | } 190 | 191 | if ro.HasLinks() { 192 | rpbLinks := make([]*rpbRiakKV.RpbLink, len(ro.Links)) 193 | for i, link := range ro.Links { 194 | rpbLinks[i] = &rpbRiakKV.RpbLink{ 195 | Bucket: []byte(link.Bucket), 196 | Key: []byte(link.Key), 197 | Tag: []byte(link.Tag), 198 | } 199 | } 200 | rpbContent.Links = rpbLinks 201 | } 202 | 203 | return rpbContent, nil 204 | } 205 | -------------------------------------------------------------------------------- /cluster_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "testing" 21 | ) 22 | 23 | func TestCreateClusterWithDefaultOptions(t *testing.T) { 24 | cluster, err := NewCluster(nil) 25 | if err != nil { 26 | t.Error(err.Error()) 27 | } 28 | if cluster.nodes == nil { 29 | t.Errorf("expected non-nil value") 30 | } 31 | if expected, actual := 1, len(cluster.nodes); expected != actual { 32 | t.Errorf("expected %v, got %v", expected, actual) 33 | } 34 | defaultNodeAddr := cluster.nodes[0].addr.String() 35 | if expected, actual := defaultRemoteAddress, defaultNodeAddr; expected != actual { 36 | t.Errorf("expected %v, got %v", expected, actual) 37 | } 38 | if expected, actual := clusterCreated, cluster.getState(); expected != actual { 39 | t.Errorf("expected %v, got %v", expected, actual) 40 | } 41 | if cluster.nodeManager == nil { 42 | t.Error("expected cluster to have a node manager") 43 | } 44 | if expected, actual := defaultExecutionAttempts, cluster.executionAttempts; expected != actual { 45 | t.Errorf("expected %v, got %v", expected, actual) 46 | } 47 | } 48 | 49 | func TestCreateClusterWithoutDefaultNodeAndStart(t *testing.T) { 50 | o := &ClusterOptions{ 51 | NoDefaultNode: true, 52 | } 53 | cluster, err := NewCluster(o) 54 | if err != nil { 55 | t.Error(err.Error()) 56 | } 57 | if cluster.nodes == nil { 58 | t.Errorf("expected non-nil value") 59 | } 60 | if expected, actual := 0, len(cluster.nodes); expected != actual { 61 | t.Errorf("expected %v, got %v", expected, actual) 62 | } 63 | if expected, actual := clusterCreated, cluster.getState(); expected != actual { 64 | t.Errorf("expected %v, got %v", expected, actual) 65 | } 66 | if cluster.nodeManager == nil { 67 | t.Error("expected cluster to have a node manager") 68 | } 69 | if expected, actual := defaultExecutionAttempts, cluster.executionAttempts; expected != actual { 70 | t.Errorf("expected %v, got %v", expected, actual) 71 | } 72 | err = cluster.Start() 73 | if err != nil { 74 | t.Error(err.Error()) 75 | } 76 | if expected, actual := clusterRunning, cluster.getState(); expected != actual { 77 | t.Errorf("expected %v, got %v", expected, actual) 78 | } 79 | } 80 | 81 | func TestAddAndRemoveNodeFromCluster(t *testing.T) { 82 | var err error 83 | var c *Cluster 84 | c, err = NewCluster(nil) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | node := c.nodes[0] 90 | // re-adding same node instance won't add it 91 | if err = c.AddNode(node); err != nil { 92 | t.Fatal(err) 93 | } 94 | if expected, actual := 1, len(c.nodes); expected != actual { 95 | t.Errorf("expected %v, got %v", expected, actual) 96 | } 97 | // add 4 more nodes 98 | var addrRemoved *net.TCPAddr 99 | var nodeToRemove *Node 100 | portToRemove := 10027 101 | addrRemoved, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", portToRemove)) 102 | for port := 10017; port <= 10047; port += 10 { 103 | addr := fmt.Sprintf("127.0.0.1:%d", port) 104 | opts := &NodeOptions{ 105 | RemoteAddress: addr, 106 | } 107 | var n *Node 108 | if n, err = NewNode(opts); err != nil { 109 | t.Fatal(err) 110 | } else { 111 | if err = c.AddNode(n); err != nil { 112 | t.Fatal(err) 113 | } 114 | if port == portToRemove { 115 | nodeToRemove = n 116 | } 117 | } 118 | } 119 | if expected, actual := 5, len(c.nodes); expected != actual { 120 | t.Errorf("expected %v, got %v", expected, actual) 121 | } 122 | if expected, actual := true, c.isCurrentState(clusterCreated); expected != actual { 123 | t.Errorf("expected %v, got %v", expected, actual) 124 | } 125 | for _, n := range c.nodes { 126 | if expected, actual := true, n.isCurrentState(nodeCreated); expected != actual { 127 | t.Errorf("expected %v, got %v", expected, actual) 128 | } 129 | } 130 | // remove node with port 10027 131 | if err = c.RemoveNode(nodeToRemove); err != nil { 132 | t.Fatal(err) 133 | } 134 | if expected, actual := 4, len(c.nodes); expected != actual { 135 | t.Errorf("expected %v, got %v", expected, actual) 136 | } 137 | for _, n := range c.nodes { 138 | if expected, actual := true, n.isCurrentState(nodeCreated); expected != actual { 139 | t.Errorf("expected %v, got %v", expected, actual) 140 | } 141 | if n.addr == addrRemoved { 142 | t.Errorf("node with addr %v should have been removed", addrRemoved) 143 | } 144 | } 145 | } 146 | 147 | func TestCreateClusterWithFourNodes(t *testing.T) { 148 | nodes := make([]*Node, 0, 4) 149 | for port := 10017; port <= 10047; port += 10 { 150 | addr := fmt.Sprintf("127.0.0.1:%d", port) 151 | opts := &NodeOptions{ 152 | RemoteAddress: addr, 153 | } 154 | if node, err := NewNode(opts); err != nil { 155 | t.Error(err.Error()) 156 | } else { 157 | nodes = append(nodes, node) 158 | } 159 | } 160 | 161 | opts := &ClusterOptions{ 162 | Nodes: nodes, 163 | } 164 | cluster, err := NewCluster(opts) 165 | if err != nil { 166 | t.Error(err.Error()) 167 | } 168 | if expected, actual := 4, len(cluster.nodes); expected != actual { 169 | t.Errorf("expected %v, got %v", expected, actual) 170 | } 171 | for i, node := range cluster.nodes { 172 | port := 10007 + ((i + 1) * 10) 173 | expectedAddr := fmt.Sprintf("127.0.0.1:%d", port) 174 | if expected, actual := expectedAddr, node.addr.String(); expected != actual { 175 | t.Errorf("expected %v, got %v", expected, actual) 176 | } 177 | } 178 | if expected, actual := clusterCreated, cluster.getState(); expected != actual { 179 | t.Errorf("expected %v, got %v", expected, actual) 180 | } 181 | if cluster.nodeManager == nil { 182 | t.Error("expected cluster to have a node manager") 183 | } 184 | } 185 | 186 | func ExampleNewCluster() { 187 | cluster, err := NewCluster(nil) 188 | if err != nil { 189 | panic(fmt.Sprintf("Error building cluster object: %s", err.Error())) 190 | } 191 | fmt.Println(cluster.nodes[0].addr.String()) 192 | // Output: 127.0.0.1:8087 193 | } 194 | -------------------------------------------------------------------------------- /node_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "testing" 21 | ) 22 | 23 | func TestCreateNodeWithOptions(t *testing.T) { 24 | builder := &PingCommandBuilder{} 25 | opts := &NodeOptions{ 26 | RemoteAddress: "8.8.8.8:1234", 27 | MinConnections: 2, 28 | MaxConnections: 2048, 29 | IdleTimeout: tenSeconds, 30 | ConnectTimeout: tenSeconds, 31 | RequestTimeout: tenSeconds, 32 | HealthCheckInterval: tenSeconds, 33 | HealthCheckBuilder: builder, 34 | TempNetErrorRetries: 16, 35 | } 36 | node, err := NewNode(opts) 37 | if err != nil { 38 | t.Error(err.Error()) 39 | } 40 | if expected, actual := "8.8.8.8:1234|0|0", node.String(); expected != actual { 41 | t.Errorf("expected %v, got %v", expected, actual) 42 | } 43 | if expected, actual := nodeCreated, node.getState(); expected != actual { 44 | t.Errorf("expected %v, got %v", expected, actual) 45 | } 46 | if node.addr.Port != 1234 { 47 | t.Errorf("expected port 1234, got: %s", string(node.addr.Port)) 48 | } 49 | if node.addr.Zone != "" { 50 | t.Errorf("expected empty zone, got: %s", string(node.addr.Zone)) 51 | } 52 | var testIP = net.ParseIP("8.8.8.8") 53 | if !node.addr.IP.Equal(testIP) { 54 | t.Errorf("expected %v, got: %v", testIP, node.addr.IP) 55 | } 56 | if expected, actual := node.cm.minConnections, opts.MinConnections; expected != actual { 57 | t.Errorf("expected %v, got: %v", expected, actual) 58 | } 59 | if expected, actual := node.cm.maxConnections, opts.MaxConnections; expected != actual { 60 | t.Errorf("expected %v, got: %v", expected, actual) 61 | } 62 | if expected, actual := node.cm.idleTimeout, opts.IdleTimeout; expected != actual { 63 | t.Errorf("expected %v, got: %v", expected, actual) 64 | } 65 | if expected, actual := node.cm.connectTimeout, opts.ConnectTimeout; expected != actual { 66 | t.Errorf("expected %v, got: %v", expected, actual) 67 | } 68 | if expected, actual := node.cm.requestTimeout, opts.RequestTimeout; expected != actual { 69 | t.Errorf("expected %v, got: %v", expected, actual) 70 | } 71 | if got, want := node.cm.tempNetErrorRetries, opts.TempNetErrorRetries; got != want { 72 | t.Errorf("got %v, want %v", got, want) 73 | } 74 | if expected, actual := node.healthCheckInterval, opts.HealthCheckInterval; expected != actual { 75 | t.Errorf("expected %v, got: %v", expected, actual) 76 | } 77 | if expected, actual := builder, node.healthCheckBuilder; expected != actual { 78 | t.Errorf("expected %v, got: %v", expected, actual) 79 | } 80 | } 81 | 82 | func TestEnsureNodeValuesWithZeroValOptions(t *testing.T) { 83 | opts := &NodeOptions{ 84 | TempNetErrorRetries: 8, 85 | } 86 | node, err := NewNode(opts) 87 | if err != nil { 88 | t.Error(err.Error()) 89 | } 90 | exp := fmt.Sprintf("%s|0|0", defaultRemoteAddress) 91 | if expected, actual := exp, node.String(); expected != actual { 92 | t.Errorf("expected %v, got %v", expected, actual) 93 | } 94 | if expected, actual := nodeCreated, node.getState(); expected != actual { 95 | t.Errorf("expected %v, got %v", expected, actual) 96 | } 97 | if node.addr.Port != int(defaultRemotePort) { 98 | t.Errorf("expected port %v, got: %v", defaultRemotePort, node.addr.Port) 99 | } 100 | if expected, actual := node.cm.minConnections, defaultMinConnections; expected != actual { 101 | t.Errorf("expected %v, got: %v", expected, actual) 102 | } 103 | if expected, actual := node.cm.maxConnections, defaultMaxConnections; expected != actual { 104 | t.Errorf("expected %v, got: %v", expected, actual) 105 | } 106 | if expected, actual := node.cm.idleTimeout, defaultIdleTimeout; expected != actual { 107 | t.Errorf("expected %v, got: %v", expected, actual) 108 | } 109 | if expected, actual := node.cm.connectTimeout, defaultConnectTimeout; expected != actual { 110 | t.Errorf("expected %v, got: %v", expected, actual) 111 | } 112 | if expected, actual := node.cm.requestTimeout, defaultRequestTimeout; expected != actual { 113 | t.Errorf("expected %v, got: %v", expected, actual) 114 | } 115 | if got, want := node.cm.tempNetErrorRetries, opts.TempNetErrorRetries; got != want { 116 | t.Errorf("got %v, want %v", got, want) 117 | } 118 | if expected, actual := node.healthCheckInterval, defaultHealthCheckInterval; expected != actual { 119 | t.Errorf("expected %v, got: %v", expected, actual) 120 | } 121 | } 122 | 123 | func TestEnsureDefaultNodeValues(t *testing.T) { 124 | node, err := NewNode(nil) 125 | if err != nil { 126 | t.Error(err.Error()) 127 | } 128 | if node.addr.Port != 8087 { 129 | t.Errorf("expected port 8087, got: %v", string(node.addr.Port)) 130 | } 131 | if node.addr.Zone != "" { 132 | t.Errorf("expected empty zone, got: %v", string(node.addr.Zone)) 133 | } 134 | if !node.addr.IP.Equal(localhost) { 135 | t.Errorf("expected %v, got: %v", localhost, node.addr.IP) 136 | } 137 | if expected, actual := defaultMinConnections, node.cm.minConnections; expected != actual { 138 | t.Errorf("expected %v, got: %v", expected, actual) 139 | } 140 | if expected, actual := defaultMaxConnections, node.cm.maxConnections; expected != actual { 141 | t.Errorf("expected %v, got: %v", expected, actual) 142 | } 143 | if expected, actual := defaultIdleTimeout, node.cm.idleTimeout; expected != actual { 144 | t.Errorf("expected %v, got: %v", expected, actual) 145 | } 146 | if expected, actual := defaultConnectTimeout, node.cm.connectTimeout; expected != actual { 147 | t.Errorf("expected %v, got: %v", expected, actual) 148 | } 149 | if expected, actual := defaultRequestTimeout, node.cm.requestTimeout; expected != actual { 150 | t.Errorf("expected %v, got: %v", expected, actual) 151 | } 152 | if expected, actual := defaultConnectTimeout, node.cm.connectTimeout; expected != actual { 153 | t.Errorf("expected %v, got: %v", expected, actual) 154 | } 155 | if got, want := node.cm.tempNetErrorRetries, defaultTempNetErrorRetries; got != want { 156 | t.Errorf("got %v, want %v", got, want) 157 | } 158 | if expected, actual := defaultHealthCheckInterval, node.healthCheckInterval; expected != actual { 159 | t.Errorf("expected %v, got: %v", expected, actual) 160 | } 161 | if node.healthCheckBuilder != nil { 162 | t.Errorf("expected nil, got: %v", node.healthCheckBuilder) 163 | } 164 | if expected, actual := nodeCreated, node.getState(); expected != actual { 165 | t.Errorf("expected %v, got: %v", expected, actual) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /examples/gh-47/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bufio" 19 | "crypto/rand" 20 | "encoding/hex" 21 | "errors" 22 | "fmt" 23 | "log" 24 | "os" 25 | "strconv" 26 | "time" 27 | 28 | riak "github.com/basho/riak-go-client" 29 | ) 30 | 31 | var stopping bool = false 32 | var nodeCount uint16 = 1 33 | var minConnections uint16 = 10 34 | var maxConnections uint16 = 256 35 | 36 | var key uint64 37 | var data []byte 38 | 39 | var slog = log.New(os.Stdout, "", log.LstdFlags) 40 | var elog = log.New(os.Stderr, "", log.LstdFlags) 41 | 42 | func LogInfo(source, format string, v ...interface{}) { 43 | slog.Printf(fmt.Sprintf("[INFO] %s %s", source, format), v...) 44 | } 45 | 46 | func LogDebug(source, format string, v ...interface{}) { 47 | slog.Printf(fmt.Sprintf("[DEBUG] %s %s", source, format), v...) 48 | } 49 | 50 | func LogError(source, format string, v ...interface{}) { 51 | elog.Printf(fmt.Sprintf("[DEBUG] %s %s", source, format), v...) 52 | } 53 | 54 | func LogErr(source string, err error) { 55 | elog.Println("[ERROR]", source, err) 56 | } 57 | 58 | func ErrExit(err error) { 59 | LogErr("[GH-47]", err) 60 | os.Exit(1) 61 | } 62 | 63 | func init() { 64 | c := 524288 65 | b := make([]byte, c) 66 | _, err := rand.Read(b) 67 | if err != nil { 68 | ErrExit(err) 69 | } 70 | data = []byte(hex.EncodeToString(b)) // NB: bytes of utf-8 encoding 71 | } 72 | 73 | func keepAlive(c *riak.Cluster, sc chan struct{}) { 74 | tck := time.NewTicker(time.Second * 1) 75 | dc := make(chan riak.Command, minConnections) 76 | 77 | defer func() { 78 | tck.Stop() 79 | close(dc) 80 | }() 81 | 82 | LogDebug("[GH-47/KeepAlive]", "Starting keepalive process") 83 | defer LogDebug("[GH-47/KeepAlive]", "Stopped keepalive process") 84 | 85 | for !stopping { 86 | select { 87 | case <-sc: 88 | LogDebug("[GH-47/KeepAlive]", "Stopping keepalive process") 89 | stopping = true 90 | break 91 | case pc := <-dc: 92 | LogDebug("[GH-47/KeepAlive]", "%v completed", pc.Name()) 93 | case t := <-tck.C: 94 | LogDebug("[GH-47/KeepAlive]", "Running keepalive at %v", t) 95 | for i := uint16(0); i < minConnections; i++ { 96 | if stopping { 97 | break 98 | } 99 | go func() { 100 | cmd := &riak.PingCommand{} 101 | if err := c.Execute(cmd); err != nil { 102 | LogErr("[GH-47/KeepAlive]", err) 103 | } 104 | }() 105 | /* 106 | a := &riak.Async{ 107 | Command: &riak.PingCommand{}, 108 | Done: dc, 109 | } 110 | if err := c.ExecuteAsync(a); err != nil { 111 | LogErr("[GH-47/KeepAlive]", err) 112 | } 113 | */ 114 | } 115 | } 116 | } 117 | } 118 | 119 | func storeData(c *riak.Cluster, sc chan struct{}) { 120 | tck := time.NewTicker(time.Millisecond * 125) 121 | dc := make(chan riak.Command, minConnections) 122 | 123 | defer func() { 124 | tck.Stop() 125 | close(dc) 126 | }() 127 | 128 | LogDebug("[GH-47/StoreData]", "Starting worker process") 129 | defer LogDebug("[GH-47/StoreData]", "Stopped worker process") 130 | 131 | obj := &riak.Object{ 132 | ContentType: "text/plain", 133 | Charset: "utf-8", 134 | ContentEncoding: "utf-8", 135 | Value: data, 136 | } 137 | 138 | for !stopping { 139 | select { 140 | case <-sc: 141 | LogDebug("[GH-47/StoreData]", "Stopping worker process") 142 | stopping = true 143 | break 144 | case cmd := <-dc: 145 | LogDebug("[GH-47/StoreData]", "%v completed", cmd.Name()) 146 | case <-tck.C: 147 | for i := uint16(0); i < minConnections; i++ { 148 | if stopping { 149 | break 150 | } 151 | svc, err := riak.NewStoreValueCommandBuilder(). 152 | WithBucket("gh-47"). 153 | WithKey(strconv.FormatUint(key, 10)). 154 | WithContent(obj). 155 | Build() 156 | if err != nil { 157 | ErrExit(err) 158 | } 159 | a := &riak.Async{ 160 | Command: svc, 161 | Done: dc, 162 | } 163 | if err = c.ExecuteAsync(a); err != nil { 164 | LogErr("[GH-47/StoreData]", err) 165 | } 166 | key++ 167 | } 168 | } 169 | } 170 | } 171 | 172 | func listKeys(c *riak.Cluster, sc chan struct{}) { 173 | tck := time.NewTicker(time.Millisecond * 500) 174 | dc := make(chan riak.Command, minConnections) 175 | 176 | defer func() { 177 | tck.Stop() 178 | close(dc) 179 | }() 180 | 181 | LogDebug("[GH-47/ListKeys]", "Starting worker process") 182 | defer LogDebug("[GH-47/ListKeys]", "Stopped worker process") 183 | 184 | for !stopping { 185 | select { 186 | case <-sc: 187 | LogDebug("[GH-47/ListKeys]", "Stopping worker process") 188 | stopping = true 189 | break 190 | case cmd := <-dc: 191 | lk := cmd.(*riak.ListKeysCommand) 192 | kc := 0 193 | if lk.Response != nil { 194 | kc = len(lk.Response.Keys) 195 | } 196 | LogDebug("[GH-47/ListKeys]", "%v completed, keys: %d", cmd.Name(), kc) 197 | case <-tck.C: 198 | svc, err := riak.NewListKeysCommandBuilder(). 199 | WithBucket("gh-47"). 200 | WithStreaming(false). 201 | Build() 202 | if err != nil { 203 | ErrExit(err) 204 | } 205 | a := &riak.Async{ 206 | Command: svc, 207 | Done: dc, 208 | } 209 | if err = c.ExecuteAsync(a); err != nil { 210 | LogErr("[GH-47/ListKeys]", err) 211 | } 212 | } 213 | } 214 | } 215 | 216 | func main() { 217 | riak.EnableDebugLogging = true 218 | 219 | sc := make(chan struct{}) 220 | 221 | if len(os.Args) > 1 { 222 | if i, err := strconv.Atoi(os.Args[1]); err != nil { 223 | ErrExit(err) 224 | } else { 225 | nodeCount = uint16(i) 226 | } 227 | } 228 | LogInfo("[GH-47]", "Node count: %v", nodeCount) 229 | 230 | nodes := make([]*riak.Node, nodeCount) 231 | port := 10017 232 | 233 | for i := uint16(0); i < nodeCount; i++ { 234 | var node *riak.Node 235 | var err error 236 | nodeOpts := &riak.NodeOptions{ 237 | MinConnections: minConnections, 238 | MaxConnections: maxConnections, 239 | RemoteAddress: fmt.Sprintf("riak-test:%d", port), 240 | } 241 | if node, err = riak.NewNode(nodeOpts); err != nil { 242 | ErrExit(err) 243 | } 244 | if node == nil { 245 | ErrExit(errors.New("node is nil")) 246 | } 247 | LogDebug("[GH-47]", "node: %v", node) 248 | nodes[i] = node 249 | port += 10 250 | } 251 | 252 | opts := &riak.ClusterOptions{ 253 | Nodes: nodes, 254 | ExecutionAttempts: 3, 255 | } 256 | 257 | c, err := riak.NewCluster(opts) 258 | if err != nil { 259 | ErrExit(err) 260 | } 261 | 262 | if err = c.Start(); err != nil { 263 | ErrExit(err) 264 | } 265 | 266 | go keepAlive(c, sc) 267 | go storeData(c, sc) 268 | go listKeys(c, sc) 269 | 270 | defer func() { 271 | stopping = true 272 | close(sc) 273 | if err = c.Stop(); err != nil { 274 | LogErr("[GH-47]", err) 275 | } 276 | }() 277 | 278 | LogInfo("[GH-47]", "HIT ANY KEY TO STOP") 279 | bio := bufio.NewReader(os.Stdin) 280 | _, _, rerr := bio.ReadLine() 281 | if rerr != nil { 282 | LogErr("[GH-47]", rerr) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /yz_commands_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | // FetchIndex 26 | // StoreIndex 27 | 28 | func TestStoreFetchAndDeleteAYokozunaIndex(t *testing.T) { 29 | cluster := integrationTestsBuildCluster() 30 | defer func() { 31 | if err := cluster.Stop(); err != nil { 32 | t.Error(err.Error()) 33 | } 34 | }() 35 | 36 | var err error 37 | var cmd Command 38 | indexName := "indexName" 39 | sbuilder := NewStoreIndexCommandBuilder() 40 | cmd, err = sbuilder.WithIndexName(indexName).WithTimeout(time.Second * 30).Build() 41 | if err != nil { 42 | t.Fatal(err.Error()) 43 | } 44 | if err = cluster.Execute(cmd); err != nil { 45 | t.Fatal(err.Error()) 46 | } 47 | if scmd, ok := cmd.(*StoreIndexCommand); ok { 48 | if expected, actual := true, scmd.Response; expected != actual { 49 | t.Errorf("expected %v, got %v", expected, actual) 50 | } 51 | } else { 52 | t.FailNow() 53 | } 54 | 55 | fbuilder := NewFetchIndexCommandBuilder() 56 | cmd, err = fbuilder.WithIndexName(indexName).Build() 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | if err = cluster.Execute(cmd); err != nil { 61 | t.Fatal(err.Error()) 62 | } 63 | if fcmd, ok := cmd.(*FetchIndexCommand); ok { 64 | if fcmd.Response == nil { 65 | t.Errorf("expected non-nil Response") 66 | } 67 | idx := fcmd.Response[0] 68 | if expected, actual := indexName, idx.Name; expected != actual { 69 | t.Errorf("expected %v, got %v", expected, actual) 70 | } 71 | if expected, actual := "_yz_default", idx.Schema; expected != actual { 72 | t.Errorf("expected %v, got %v", expected, actual) 73 | } 74 | if expected, actual := uint32(3), idx.NVal; expected != actual { 75 | t.Errorf("expected %v, got %v", expected, actual) 76 | } 77 | } else { 78 | t.FailNow() 79 | } 80 | 81 | dbuilder := NewDeleteIndexCommandBuilder() 82 | cmd, err = dbuilder.WithIndexName(indexName).Build() 83 | if err != nil { 84 | t.Fatal(err.Error()) 85 | } 86 | if err = cluster.Execute(cmd); err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | if dcmd, ok := cmd.(*DeleteIndexCommand); ok { 90 | if expected, actual := true, dcmd.Response; expected != actual { 91 | t.Errorf("expected %v, got %v", expected, actual) 92 | } 93 | } else { 94 | t.FailNow() 95 | } 96 | } 97 | 98 | // FetchSchema 99 | // StoreSchema 100 | 101 | func TestStoreFetchAndDeleteAYokozunaSchema(t *testing.T) { 102 | cluster := integrationTestsBuildCluster() 103 | defer func() { 104 | if err := cluster.Stop(); err != nil { 105 | t.Error(err.Error()) 106 | } 107 | }() 108 | 109 | var err error 110 | var cmd Command 111 | defaultSchemaName := "_yz_default" 112 | schemaName := "schemaName" 113 | schemaXml := "dummy" 114 | 115 | fbuilder := NewFetchSchemaCommandBuilder() 116 | cmd, err = fbuilder.WithSchemaName(defaultSchemaName).Build() 117 | if err != nil { 118 | t.Fatal(err.Error()) 119 | } 120 | if err = cluster.Execute(cmd); err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | fcmd := cmd.(*FetchSchemaCommand) 125 | if fcmd.Response == nil { 126 | t.Fatal("expected non-nil Response") 127 | } 128 | 129 | sch := fcmd.Response 130 | if expected, actual := defaultSchemaName, sch.Name; expected != actual { 131 | t.Errorf("expected %v, got %v", expected, actual) 132 | } 133 | 134 | schemaXml = sch.Content 135 | 136 | sbuilder := NewStoreSchemaCommandBuilder() 137 | cmd, err = sbuilder.WithSchemaName(schemaName).WithSchema(schemaXml).Build() 138 | if err != nil { 139 | t.Fatal(err.Error()) 140 | } 141 | if err = cluster.Execute(cmd); err != nil { 142 | t.Fatal(err.Error()) 143 | } 144 | if scmd, ok := cmd.(*StoreSchemaCommand); ok { 145 | if expected, actual := true, scmd.Response; expected != actual { 146 | t.Errorf("expected %v, got %v", expected, actual) 147 | } 148 | } else { 149 | t.FailNow() 150 | } 151 | } 152 | 153 | // Search 154 | 155 | func TestSearchViaYokozunaIndex(t *testing.T) { 156 | cluster := integrationTestsBuildCluster() 157 | defer func() { 158 | if err := cluster.Stop(); err != nil { 159 | t.Error(err.Error()) 160 | } 161 | }() 162 | 163 | var err error 164 | var cmd Command 165 | indexName := "myIndex" 166 | 167 | b1 := NewStoreIndexCommandBuilder() 168 | cmd, err = b1.WithIndexName(indexName).WithTimeout(time.Second * 20).Build() 169 | if err != nil { 170 | t.Fatal(err.Error()) 171 | } 172 | if err = cluster.Execute(cmd); err != nil { 173 | t.Fatal(err.Error()) 174 | } 175 | if scmd, ok := cmd.(*StoreIndexCommand); ok { 176 | if expected, actual := true, scmd.Response; expected != actual { 177 | t.Errorf("expected %v, got %v", expected, actual) 178 | } 179 | } else { 180 | t.FailNow() 181 | } 182 | 183 | searchBucket := fmt.Sprintf("%s_search", testBucketName) 184 | b2 := NewStoreBucketPropsCommandBuilder() 185 | cmd, err = b2.WithBucket(searchBucket).WithSearchIndex(indexName).Build() 186 | if err != nil { 187 | t.Fatal(err.Error()) 188 | } 189 | if err = cluster.Execute(cmd); err != nil { 190 | t.Fatal(err.Error()) 191 | } 192 | 193 | stuffToStore := [...]string{ 194 | "{ \"content_s\":\"Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice 'without pictures or conversation?'\"}", 195 | "{ \"content_s\":\"So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her.\", \"multi_ss\":[\"this\",\"that\"]}", 196 | "{ \"content_s\":\"The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down a very deep well.\"}", 197 | } 198 | 199 | for _, s := range stuffToStore { 200 | b3 := NewStoreValueCommandBuilder() 201 | obj := &Object{ 202 | ContentType: "application/json", // NB: *very* important for extractor 203 | Value: []byte(s), 204 | } 205 | cmd, err = b3.WithBucket(searchBucket).WithContent(obj).Build() 206 | if err != nil { 207 | t.Fatal(err.Error()) 208 | } 209 | if err = cluster.Execute(cmd); err != nil { 210 | t.Fatal(err.Error()) 211 | } 212 | } 213 | 214 | for count := 0; count < 10; count++ { 215 | time.Sleep(time.Millisecond * 500) 216 | b4 := NewSearchCommandBuilder() 217 | cmd, err = b4.WithIndexName(indexName).WithQuery("multi_ss:t*").Build() 218 | if err != nil { 219 | t.Fatal(err.Error()) 220 | } 221 | if err = cluster.Execute(cmd); err != nil { 222 | t.Fatal(err.Error()) 223 | } 224 | if scmd, ok := cmd.(*SearchCommand); ok { 225 | resp := scmd.Response 226 | if expected, actual := 1, len(resp.Docs); expected != actual { 227 | if count < 10 { 228 | t.Logf("expected %v, got %v - RETRYING", expected, actual) 229 | } else { 230 | t.Errorf("expected %v, got %v - DONE", expected, actual) 231 | } 232 | } 233 | } else { 234 | t.FailNow() 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /globals_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration timeseries integration_hll 16 | 17 | package riak 18 | 19 | import ( 20 | "bytes" 21 | "encoding/binary" 22 | "errors" 23 | "fmt" 24 | "io" 25 | "net" 26 | "reflect" 27 | "strconv" 28 | "sync" 29 | "testing" 30 | 31 | rpb_riak "github.com/basho/riak-go-client/rpb/riak" 32 | proto "github.com/golang/protobuf/proto" 33 | ) 34 | 35 | func integrationTestsBuildCluster() *Cluster { 36 | var cluster *Cluster 37 | var err error 38 | nodeOpts := &NodeOptions{ 39 | RemoteAddress: getRiakAddress(), 40 | } 41 | var node *Node 42 | node, err = NewNode(nodeOpts) 43 | if err != nil { 44 | panic(fmt.Sprintf("error building integration test node object: %s", err.Error())) 45 | } 46 | if node == nil { 47 | panic("NewNode returned nil!") 48 | } 49 | nodes := []*Node{node} 50 | opts := &ClusterOptions{ 51 | Nodes: nodes, 52 | } 53 | cluster, err = NewCluster(opts) 54 | if err != nil { 55 | panic(fmt.Sprintf("error building integration test cluster object: %s", err.Error())) 56 | } 57 | if err = cluster.Start(); err != nil { 58 | panic(fmt.Sprintf("error starting integration test cluster object: %s", err.Error())) 59 | } 60 | return cluster 61 | } 62 | 63 | type testListenerOpts struct { 64 | test *testing.T 65 | host string 66 | port uint16 67 | onConn func(c net.Conn) bool 68 | } 69 | 70 | type testListener struct { 71 | test *testing.T 72 | host string 73 | port uint16 74 | addr net.Addr 75 | onConn func(c net.Conn) bool 76 | ln net.Listener 77 | } 78 | 79 | func newTestListener(o *testListenerOpts) *testListener { 80 | if o.test == nil { 81 | panic("testing object is required") 82 | } 83 | if o.host == "" { 84 | o.host = "127.0.0.1" 85 | } 86 | if o.onConn == nil { 87 | o.onConn = func(c net.Conn) bool { 88 | if readWriteResp(o.test, c, false) { 89 | return false // connection is not done 90 | } 91 | return true // connection is done 92 | } 93 | } 94 | t := &testListener{ 95 | test: o.test, 96 | host: o.host, 97 | port: o.port, 98 | onConn: o.onConn, 99 | } 100 | if t.port > 0 { 101 | addrstr := net.JoinHostPort(t.host, strconv.Itoa(int(t.port))) 102 | if addr, err := net.ResolveTCPAddr("tcp4", addrstr); err != nil { 103 | t.test.Fatal(err) 104 | } else { 105 | t.addr = addr 106 | } 107 | } 108 | return t 109 | } 110 | 111 | func (t *testListener) start() { 112 | if t.test == nil { 113 | panic("testing object is required") 114 | } 115 | 116 | wg := &sync.WaitGroup{} 117 | wg.Add(1) 118 | 119 | addr := net.JoinHostPort(t.host, strconv.Itoa(int(t.port))) 120 | ln, err := net.Listen("tcp", addr) 121 | if err != nil { 122 | t.test.Fatal(err) 123 | } else { 124 | t.ln = ln 125 | t.addr = ln.Addr() 126 | tcpaddr := t.addr.(*net.TCPAddr) 127 | t.port = uint16(tcpaddr.Port) 128 | } 129 | 130 | go func() { 131 | wg.Done() 132 | logDebug("[testListener]", "(%v) started", t.addr) 133 | for { 134 | c, err := t.ln.Accept() 135 | if err != nil { 136 | if _, ok := err.(*net.OpError); !ok { 137 | t.test.Log(err) 138 | } 139 | return 140 | } 141 | go func() { 142 | for { 143 | if t.onConn(c) { 144 | break 145 | } 146 | } 147 | }() 148 | } 149 | }() 150 | 151 | wg.Wait() 152 | return 153 | } 154 | 155 | func (t *testListener) stop() { 156 | if t.ln == nil { 157 | logDebugln("[testListener]", "never started!") 158 | } else { 159 | if err := t.ln.Close(); err != nil { 160 | t.test.Error(err) 161 | } 162 | logDebug("[testListener]", "(%v) stopped", t.addr) 163 | } 164 | } 165 | 166 | func readWriteResp(t *testing.T, c net.Conn, shouldClose bool) (success bool) { 167 | success = false 168 | var err error 169 | var msgCode byte 170 | 171 | if msgCode, err = readClientMessage(c); err != nil { 172 | if err == io.EOF { 173 | c.Close() 174 | } else { 175 | logErr("[testListener]", err) 176 | t.Error(err) 177 | } 178 | success = false 179 | return 180 | } 181 | 182 | var data []byte 183 | switch msgCode { 184 | case rpbCode_RpbPingReq: 185 | data = buildRiakMessage(rpbCode_RpbPingResp, nil) 186 | case rpbCode_RpbGetServerInfoReq: 187 | data, err = buildGetServerInfoResp() 188 | default: 189 | msg := fmt.Sprintf("unknown msg code: %v", msgCode) 190 | data, err = buildRiakError(msg) 191 | } 192 | 193 | if err != nil { 194 | t.Error(err) 195 | success = false 196 | } 197 | 198 | count, err := c.Write(data) 199 | if err == nil { 200 | success = true 201 | } else { 202 | t.Error(err) 203 | success = false 204 | } 205 | 206 | if count != len(data) { 207 | t.Errorf("expected to write %v bytes, wrote %v bytes", len(data), count) 208 | success = false 209 | } 210 | 211 | if shouldClose { 212 | c.Close() 213 | } 214 | 215 | return 216 | } 217 | 218 | // TODO this is copied from connection.go and should be shared 219 | func readClientMessage(c net.Conn) (msgCode byte, err error) { 220 | var sizeBuf []byte = make([]byte, 4) 221 | var count int = 0 222 | if count, err = io.ReadFull(c, sizeBuf); err == nil && count == 4 { 223 | messageLength := binary.BigEndian.Uint32(sizeBuf) 224 | data := make([]byte, messageLength) 225 | count, err = io.ReadFull(c, data) 226 | if err != nil { 227 | return 228 | } else if uint32(count) != messageLength { 229 | err = fmt.Errorf("[readClientMessage] message length: %d, only read: %d", messageLength, count) 230 | } 231 | msgCode = data[0] 232 | } else { 233 | if err != io.EOF { 234 | err = errors.New(fmt.Sprintf("[readClientMessage] error reading command size into sizeBuf: count %d, err %s, errtype %v", count, err, reflect.TypeOf(err))) 235 | } 236 | } 237 | return 238 | } 239 | 240 | func handleClientMessageWithRiakError(t *testing.T, c net.Conn, msgCount uint16, respChan chan bool) { 241 | defer func() { 242 | if err := c.Close(); err != nil { 243 | t.Error(err) 244 | } 245 | }() 246 | 247 | for i := 0; i < int(msgCount); i++ { 248 | if _, err := readClientMessage(c); err != nil { 249 | t.Error(err) 250 | } 251 | 252 | data, err := buildRiakError("this is an error") 253 | if err != nil { 254 | t.Error(err) 255 | } 256 | 257 | count, err := c.Write(data) 258 | if err != nil { 259 | t.Error(err) 260 | } 261 | if count != len(data) { 262 | t.Errorf("expected to write %v bytes, wrote %v bytes", len(data), count) 263 | } 264 | if respChan != nil { 265 | respChan <- true 266 | } 267 | } 268 | } 269 | 270 | func buildGetServerInfoResp() ([]byte, error) { 271 | n := bytes.NewBufferString("golang-test") 272 | v := bytes.NewBufferString("9.9.9") 273 | rpb := &rpb_riak.RpbGetServerInfoResp{ 274 | Node: n.Bytes(), 275 | ServerVersion: v.Bytes(), 276 | } 277 | if encoded, err := proto.Marshal(rpb); err != nil { 278 | return nil, err 279 | } else { 280 | data := buildRiakMessage(rpbCode_RpbGetServerInfoResp, encoded) 281 | return data, nil 282 | } 283 | } 284 | 285 | func buildRiakError(errmsg string) ([]byte, error) { 286 | var errcode uint32 = 1 287 | emsg := bytes.NewBufferString(errmsg) 288 | rpbErr := &rpb_riak.RpbErrorResp{ 289 | Errcode: &errcode, 290 | Errmsg: emsg.Bytes(), 291 | } 292 | if encoded, err := proto.Marshal(rpbErr); err != nil { 293 | return nil, err 294 | } else { 295 | data := buildRiakMessage(rpbCode_RpbErrorResp, encoded) 296 | return data, nil 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /examples/dev/using/search/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "os" 21 | "sync" 22 | "time" 23 | 24 | riak "github.com/basho/riak-go-client" 25 | ) 26 | 27 | /* 28 | Code samples from: 29 | http://docs.basho.com/riak/latest/dev/using/search/ 30 | 31 | make sure these bucket-types are created: 32 | 'animals', 'quotes', 'sports', 'cars', 'users', 'n_val_of_5' 33 | */ 34 | 35 | func main() { 36 | //riak.EnableDebugLogging = true 37 | 38 | nodeOpts := &riak.NodeOptions{ 39 | RemoteAddress: "riak-test:10017", 40 | RequestTimeout: time.Second * 60, 41 | } 42 | 43 | var node *riak.Node 44 | var err error 45 | if node, err = riak.NewNode(nodeOpts); err != nil { 46 | fmt.Println(err.Error()) 47 | } 48 | 49 | nodes := []*riak.Node{node} 50 | opts := &riak.ClusterOptions{ 51 | Nodes: nodes, 52 | } 53 | 54 | cluster, err := riak.NewCluster(opts) 55 | if err != nil { 56 | fmt.Println(err.Error()) 57 | } 58 | 59 | defer func() { 60 | if err = cluster.Stop(); err != nil { 61 | fmt.Println(err.Error()) 62 | } 63 | }() 64 | 65 | if err = cluster.Start(); err != nil { 66 | fmt.Println(err.Error()) 67 | } 68 | 69 | // ping 70 | ping := &riak.PingCommand{} 71 | if err = cluster.Execute(ping); err != nil { 72 | fmt.Println(err.Error()) 73 | } else { 74 | fmt.Println("ping passed") 75 | } 76 | 77 | if err = storeIndex(cluster); err != nil { 78 | ErrExit(err) 79 | } 80 | 81 | if err = storeBucketProperties(cluster); err != nil { 82 | ErrExit(err) 83 | } 84 | 85 | if err = storeObjects(cluster); err != nil { 86 | ErrExit(err) 87 | } 88 | 89 | time.Sleep(time.Millisecond * 1250) 90 | 91 | if err = doSearchRequest(cluster); err != nil { 92 | ErrExit(err) 93 | } 94 | 95 | if err = doAgeSearchRequest(cluster); err != nil { 96 | ErrExit(err) 97 | } 98 | 99 | if err = doAndSearchRequest(cluster); err != nil { 100 | ErrExit(err) 101 | } 102 | 103 | if err = doPaginatedSearchRequest(cluster); err != nil { 104 | ErrExit(err) 105 | } 106 | 107 | if err = deleteIndex(cluster); err != nil { 108 | ErrExit(err) 109 | } 110 | } 111 | 112 | func ErrExit(err error) { 113 | os.Stderr.WriteString(err.Error()) 114 | os.Exit(1) 115 | } 116 | 117 | func storeIndex(cluster *riak.Cluster) error { 118 | cmd, err := riak.NewStoreIndexCommandBuilder(). 119 | WithIndexName("famous"). 120 | WithSchemaName("_yz_default"). 121 | WithTimeout(time.Second * 30). 122 | Build() 123 | if err != nil { 124 | return err 125 | } 126 | 127 | return cluster.Execute(cmd) 128 | } 129 | 130 | func storeBucketProperties(cluster *riak.Cluster) error { 131 | cmd, err := riak.NewStoreBucketPropsCommandBuilder(). 132 | WithBucketType("animals"). 133 | WithBucket("cats"). 134 | WithSearchIndex("famous"). 135 | Build() 136 | if err != nil { 137 | return err 138 | } 139 | 140 | return cluster.Execute(cmd) 141 | } 142 | 143 | func storeObjects(cluster *riak.Cluster) error { 144 | o1 := &riak.Object{ 145 | Key: "liono", 146 | Value: []byte("{\"name_s\":\"Lion-o\",\"age_i\":30,\"leader_b\":true}"), 147 | } 148 | o2 := &riak.Object{ 149 | Key: "cheetara", 150 | Value: []byte("{\"name_s\":\"Cheetara\",\"age_i\":30,\"leader_b\":false}"), 151 | } 152 | o3 := &riak.Object{ 153 | Key: "snarf", 154 | Value: []byte("{\"name_s\":\"Snarf\",\"age_i\":43,\"leader_b\":false}"), 155 | } 156 | o4 := &riak.Object{ 157 | Key: "panthro", 158 | Value: []byte("{\"name_s\":\"Panthro\",\"age_i\":36,\"leader_b\":false}"), 159 | } 160 | 161 | objs := [...]*riak.Object{o1, o2, o3, o4} 162 | 163 | wg := &sync.WaitGroup{} 164 | for _, obj := range objs { 165 | obj.ContentType = "application/json" 166 | obj.Charset = "utf-8" 167 | obj.ContentEncoding = "utf-8" 168 | 169 | cmd, err := riak.NewStoreValueCommandBuilder(). 170 | WithBucketType("animals"). 171 | WithBucket("cats"). 172 | WithContent(obj). 173 | Build() 174 | if err != nil { 175 | return err 176 | } 177 | 178 | args := &riak.Async{ 179 | Command: cmd, 180 | Wait: wg, 181 | } 182 | if err = cluster.ExecuteAsync(args); err != nil { 183 | return err 184 | } 185 | } 186 | 187 | wg.Wait() 188 | 189 | return nil 190 | } 191 | 192 | func printDocs(cmd riak.Command, desc string) error { 193 | sc := cmd.(*riak.SearchCommand) 194 | if json, jerr := json.MarshalIndent(sc.Response.Docs, "", " "); jerr != nil { 195 | return jerr 196 | } else { 197 | fmt.Println("------------------------------------------------------------------------") 198 | fmt.Println(desc) 199 | fmt.Println(string(json)) 200 | } 201 | return nil 202 | } 203 | 204 | func doSearchRequest(cluster *riak.Cluster) error { 205 | cmd, err := riak.NewSearchCommandBuilder(). 206 | WithIndexName("famous"). 207 | WithQuery("name_s:Lion*"). 208 | Build() 209 | if err != nil { 210 | return err 211 | } 212 | 213 | if err = cluster.Execute(cmd); err != nil { 214 | return err 215 | } 216 | 217 | if err = printDocs(cmd, "Search Request Documents:"); err != nil { 218 | return err 219 | } 220 | 221 | sc := cmd.(*riak.SearchCommand) 222 | doc := sc.Response.Docs[0] // NB: SearchDoc struct type 223 | 224 | cmd, err = riak.NewFetchValueCommandBuilder(). 225 | WithBucketType(doc.BucketType). 226 | WithBucket(doc.Bucket). 227 | WithKey(doc.Key). 228 | Build() 229 | if err != nil { 230 | return err 231 | } 232 | 233 | if err = cluster.Execute(cmd); err != nil { 234 | return err 235 | } 236 | 237 | fc := cmd.(*riak.FetchValueCommand) 238 | if json, jerr := json.MarshalIndent(fc.Response, "", " "); jerr != nil { 239 | return jerr 240 | } else { 241 | fmt.Println(string(json)) 242 | } 243 | 244 | return nil 245 | } 246 | 247 | func doAgeSearchRequest(cluster *riak.Cluster) error { 248 | cmd, err := riak.NewSearchCommandBuilder(). 249 | WithIndexName("famous"). 250 | WithQuery("age_i:[30 TO *]"). 251 | Build() 252 | if err != nil { 253 | return err 254 | } 255 | 256 | if err = cluster.Execute(cmd); err != nil { 257 | return err 258 | } 259 | 260 | return printDocs(cmd, "Age Search Documents:") 261 | } 262 | 263 | func doAndSearchRequest(cluster *riak.Cluster) error { 264 | cmd, err := riak.NewSearchCommandBuilder(). 265 | WithIndexName("famous"). 266 | WithQuery("leader_b:true AND age_i:[30 TO *]"). 267 | Build() 268 | if err != nil { 269 | return err 270 | } 271 | 272 | if err = cluster.Execute(cmd); err != nil { 273 | return err 274 | } 275 | 276 | return printDocs(cmd, "AND Search Documents:") 277 | } 278 | 279 | func doPaginatedSearchRequest(cluster *riak.Cluster) error { 280 | rowsPerPage := uint32(2) 281 | page := uint32(2) 282 | start := rowsPerPage * (page - uint32(1)) 283 | 284 | cmd, err := riak.NewSearchCommandBuilder(). 285 | WithIndexName("famous"). 286 | WithQuery("*:*"). 287 | WithStart(start). 288 | WithNumRows(rowsPerPage). 289 | Build() 290 | if err != nil { 291 | return err 292 | } 293 | 294 | if err = cluster.Execute(cmd); err != nil { 295 | return err 296 | } 297 | 298 | return printDocs(cmd, "Paginated Search Documents:") 299 | } 300 | 301 | func deleteIndex(cluster *riak.Cluster) error { 302 | cmd, err := riak.NewStoreBucketPropsCommandBuilder(). 303 | WithBucketType("animals"). 304 | WithBucket("cats"). 305 | WithSearchIndex("_dont_index_"). 306 | Build() 307 | if err != nil { 308 | return err 309 | } 310 | 311 | if err = cluster.Execute(cmd); err != nil { 312 | return err 313 | } 314 | 315 | cmd, err = riak.NewDeleteIndexCommandBuilder(). 316 | WithIndexName("famous"). 317 | Build() 318 | if err != nil { 319 | return err 320 | } 321 | 322 | return cluster.Execute(cmd) 323 | } 324 | -------------------------------------------------------------------------------- /rpb/riak_search/riak_search.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. 16 | // source: riak_search.proto 17 | // DO NOT EDIT! 18 | 19 | /* 20 | Package riak_search is a generated protocol buffer package. 21 | 22 | It is generated from these files: 23 | riak_search.proto 24 | 25 | It has these top-level messages: 26 | RpbSearchDoc 27 | RpbSearchQueryReq 28 | RpbSearchQueryResp 29 | */ 30 | package riak_search 31 | 32 | import proto "github.com/golang/protobuf/proto" 33 | import fmt "fmt" 34 | import math "math" 35 | import riak "github.com/basho/riak-go-client/rpb/riak" 36 | 37 | // Reference imports to suppress errors if they are not otherwise used. 38 | var _ = proto.Marshal 39 | var _ = fmt.Errorf 40 | var _ = math.Inf 41 | 42 | // This is a compile-time assertion to ensure that this generated file 43 | // is compatible with the proto package it is being compiled against. 44 | // A compilation error at this line likely means your copy of the 45 | // proto package needs to be updated. 46 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 47 | 48 | type RpbSearchDoc struct { 49 | Fields []*riak.RpbPair `protobuf:"bytes,1,rep,name=fields" json:"fields,omitempty"` 50 | XXX_unrecognized []byte `json:"-"` 51 | } 52 | 53 | func (m *RpbSearchDoc) Reset() { *m = RpbSearchDoc{} } 54 | func (m *RpbSearchDoc) String() string { return proto.CompactTextString(m) } 55 | func (*RpbSearchDoc) ProtoMessage() {} 56 | func (*RpbSearchDoc) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 57 | 58 | func (m *RpbSearchDoc) GetFields() []*riak.RpbPair { 59 | if m != nil { 60 | return m.Fields 61 | } 62 | return nil 63 | } 64 | 65 | type RpbSearchQueryReq struct { 66 | Q []byte `protobuf:"bytes,1,req,name=q" json:"q,omitempty"` 67 | Index []byte `protobuf:"bytes,2,req,name=index" json:"index,omitempty"` 68 | Rows *uint32 `protobuf:"varint,3,opt,name=rows" json:"rows,omitempty"` 69 | Start *uint32 `protobuf:"varint,4,opt,name=start" json:"start,omitempty"` 70 | Sort []byte `protobuf:"bytes,5,opt,name=sort" json:"sort,omitempty"` 71 | Filter []byte `protobuf:"bytes,6,opt,name=filter" json:"filter,omitempty"` 72 | Df []byte `protobuf:"bytes,7,opt,name=df" json:"df,omitempty"` 73 | Op []byte `protobuf:"bytes,8,opt,name=op" json:"op,omitempty"` 74 | Fl [][]byte `protobuf:"bytes,9,rep,name=fl" json:"fl,omitempty"` 75 | Presort []byte `protobuf:"bytes,10,opt,name=presort" json:"presort,omitempty"` 76 | XXX_unrecognized []byte `json:"-"` 77 | } 78 | 79 | func (m *RpbSearchQueryReq) Reset() { *m = RpbSearchQueryReq{} } 80 | func (m *RpbSearchQueryReq) String() string { return proto.CompactTextString(m) } 81 | func (*RpbSearchQueryReq) ProtoMessage() {} 82 | func (*RpbSearchQueryReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 83 | 84 | func (m *RpbSearchQueryReq) GetQ() []byte { 85 | if m != nil { 86 | return m.Q 87 | } 88 | return nil 89 | } 90 | 91 | func (m *RpbSearchQueryReq) GetIndex() []byte { 92 | if m != nil { 93 | return m.Index 94 | } 95 | return nil 96 | } 97 | 98 | func (m *RpbSearchQueryReq) GetRows() uint32 { 99 | if m != nil && m.Rows != nil { 100 | return *m.Rows 101 | } 102 | return 0 103 | } 104 | 105 | func (m *RpbSearchQueryReq) GetStart() uint32 { 106 | if m != nil && m.Start != nil { 107 | return *m.Start 108 | } 109 | return 0 110 | } 111 | 112 | func (m *RpbSearchQueryReq) GetSort() []byte { 113 | if m != nil { 114 | return m.Sort 115 | } 116 | return nil 117 | } 118 | 119 | func (m *RpbSearchQueryReq) GetFilter() []byte { 120 | if m != nil { 121 | return m.Filter 122 | } 123 | return nil 124 | } 125 | 126 | func (m *RpbSearchQueryReq) GetDf() []byte { 127 | if m != nil { 128 | return m.Df 129 | } 130 | return nil 131 | } 132 | 133 | func (m *RpbSearchQueryReq) GetOp() []byte { 134 | if m != nil { 135 | return m.Op 136 | } 137 | return nil 138 | } 139 | 140 | func (m *RpbSearchQueryReq) GetFl() [][]byte { 141 | if m != nil { 142 | return m.Fl 143 | } 144 | return nil 145 | } 146 | 147 | func (m *RpbSearchQueryReq) GetPresort() []byte { 148 | if m != nil { 149 | return m.Presort 150 | } 151 | return nil 152 | } 153 | 154 | type RpbSearchQueryResp struct { 155 | Docs []*RpbSearchDoc `protobuf:"bytes,1,rep,name=docs" json:"docs,omitempty"` 156 | MaxScore *float32 `protobuf:"fixed32,2,opt,name=max_score" json:"max_score,omitempty"` 157 | NumFound *uint32 `protobuf:"varint,3,opt,name=num_found" json:"num_found,omitempty"` 158 | XXX_unrecognized []byte `json:"-"` 159 | } 160 | 161 | func (m *RpbSearchQueryResp) Reset() { *m = RpbSearchQueryResp{} } 162 | func (m *RpbSearchQueryResp) String() string { return proto.CompactTextString(m) } 163 | func (*RpbSearchQueryResp) ProtoMessage() {} 164 | func (*RpbSearchQueryResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 165 | 166 | func (m *RpbSearchQueryResp) GetDocs() []*RpbSearchDoc { 167 | if m != nil { 168 | return m.Docs 169 | } 170 | return nil 171 | } 172 | 173 | func (m *RpbSearchQueryResp) GetMaxScore() float32 { 174 | if m != nil && m.MaxScore != nil { 175 | return *m.MaxScore 176 | } 177 | return 0 178 | } 179 | 180 | func (m *RpbSearchQueryResp) GetNumFound() uint32 { 181 | if m != nil && m.NumFound != nil { 182 | return *m.NumFound 183 | } 184 | return 0 185 | } 186 | 187 | func init() { 188 | proto.RegisterType((*RpbSearchDoc)(nil), "RpbSearchDoc") 189 | proto.RegisterType((*RpbSearchQueryReq)(nil), "RpbSearchQueryReq") 190 | proto.RegisterType((*RpbSearchQueryResp)(nil), "RpbSearchQueryResp") 191 | } 192 | 193 | func init() { proto.RegisterFile("riak_search.proto", fileDescriptor0) } 194 | 195 | var fileDescriptor0 = []byte{ 196 | // 268 bytes of a gzipped FileDescriptorProto 197 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x5c, 0x50, 0xcb, 0x4e, 0x84, 0x30, 198 | 0x14, 0x4d, 0x19, 0xe6, 0xc1, 0xb5, 0x68, 0xe8, 0xc6, 0x1b, 0xdd, 0x34, 0x6c, 0xec, 0x8a, 0x85, 199 | 0x9f, 0x40, 0xfc, 0x80, 0x11, 0x17, 0x2e, 0x49, 0x81, 0x92, 0x21, 0x03, 0xb4, 0xd3, 0x42, 0x1c, 200 | 0x3f, 0xc6, 0x7f, 0x35, 0x40, 0xd0, 0xe8, 0xee, 0x3c, 0x9a, 0xd3, 0x73, 0x2e, 0x44, 0xb6, 0x91, 201 | 0xe7, 0xdc, 0x29, 0x69, 0xcb, 0x53, 0x62, 0xac, 0x1e, 0xf4, 0x03, 0x4c, 0xd2, 0x82, 0x63, 0x01, 202 | 0x34, 0x33, 0xc5, 0xdb, 0x6c, 0xbf, 0xe8, 0x92, 0x21, 0xec, 0xea, 0x46, 0xb5, 0x95, 0x43, 0xc2, 203 | 0x37, 0xe2, 0xe6, 0xf9, 0x90, 0x64, 0xa6, 0x38, 0xca, 0xc6, 0xc6, 0x5f, 0x04, 0xa2, 0x9f, 0xa7, 204 | 0xaf, 0xa3, 0xb2, 0x9f, 0x99, 0xba, 0xb0, 0x00, 0xc8, 0x05, 0x09, 0xf7, 0x04, 0x65, 0x21, 0x6c, 205 | 0x9b, 0xbe, 0x52, 0x57, 0xf4, 0x66, 0x4a, 0xc1, 0xb7, 0xfa, 0xc3, 0xe1, 0x86, 0x13, 0x11, 0x4e, 206 | 0xa6, 0x1b, 0xa4, 0x1d, 0xd0, 0x9f, 0x29, 0x05, 0xdf, 0x69, 0x3b, 0xe0, 0x96, 0x13, 0x41, 0xd9, 207 | 0xed, 0xf4, 0x69, 0x3b, 0x28, 0x8b, 0xbb, 0x99, 0x03, 0x78, 0x55, 0x8d, 0xfb, 0x15, 0x6b, 0x83, 208 | 0x87, 0x15, 0xd7, 0x2d, 0x06, 0x7c, 0x23, 0x28, 0xbb, 0x83, 0xbd, 0xb1, 0x6a, 0x0e, 0x81, 0xc9, 209 | 0x8c, 0xdf, 0x81, 0xfd, 0xaf, 0xe7, 0x0c, 0x7b, 0x04, 0xbf, 0xd2, 0xe5, 0xba, 0x26, 0x4c, 0xfe, 210 | 0x8c, 0x8d, 0x20, 0xe8, 0xe4, 0x35, 0x77, 0xa5, 0xb6, 0x0a, 0x3d, 0x4e, 0x84, 0x37, 0x49, 0xfd, 211 | 0xd8, 0xe5, 0xb5, 0x1e, 0xfb, 0x6a, 0xa9, 0x9e, 0x3e, 0xc1, 0x7d, 0xa9, 0xbb, 0xa4, 0x90, 0xee, 212 | 0xa4, 0x93, 0xdf, 0xd3, 0x15, 0x63, 0x9d, 0xd2, 0xac, 0x91, 0xe7, 0x25, 0xef, 0x98, 0x7e, 0x07, 213 | 0x00, 0x00, 0xff, 0xff, 0xf4, 0xd4, 0x3b, 0x37, 0x6b, 0x01, 0x00, 0x00, 214 | } 215 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "crypto/tls" 19 | "encoding/binary" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "net" 24 | "time" 25 | 26 | backoff "github.com/basho/backoff" 27 | proto "github.com/golang/protobuf/proto" 28 | ) 29 | 30 | // Connection errors 31 | var ( 32 | ErrCannotRead = errors.New("Cannot read from a non-active or closed connection") 33 | ErrCannotWrite = errors.New("Cannot write to a non-active or closed connection") 34 | ) 35 | 36 | // AuthOptions object contains the authentication credentials and tls config 37 | type AuthOptions struct { 38 | User string 39 | Password string 40 | TlsConfig *tls.Config 41 | } 42 | 43 | type connectionOptions struct { 44 | remoteAddress *net.TCPAddr 45 | connectTimeout time.Duration 46 | requestTimeout time.Duration 47 | authOptions *AuthOptions 48 | tempNetErrorRetries uint16 49 | } 50 | 51 | const ( 52 | connCreated state = iota 53 | connTlsStarting 54 | connActive 55 | connInactive 56 | ) 57 | 58 | type connection struct { 59 | addr *net.TCPAddr 60 | conn net.Conn 61 | connectTimeout time.Duration 62 | requestTimeout time.Duration 63 | tempNetErrorRetries uint16 64 | authOptions *AuthOptions 65 | sizeBuf []byte 66 | dataBuf []byte 67 | active bool 68 | inFlight bool 69 | lastUsed time.Time 70 | stateData 71 | } 72 | 73 | func newConnection(options *connectionOptions) (*connection, error) { 74 | if options == nil { 75 | return nil, ErrOptionsRequired 76 | } 77 | if options.remoteAddress == nil { 78 | return nil, ErrAddressRequired 79 | } 80 | if options.connectTimeout == 0 { 81 | options.connectTimeout = defaultConnectTimeout 82 | } 83 | if options.requestTimeout == 0 { 84 | options.requestTimeout = defaultRequestTimeout 85 | } 86 | if options.tempNetErrorRetries == 0 { 87 | options.tempNetErrorRetries = defaultTempNetErrorRetries 88 | } 89 | c := &connection{ 90 | addr: options.remoteAddress, 91 | connectTimeout: options.connectTimeout, 92 | requestTimeout: options.requestTimeout, 93 | tempNetErrorRetries: options.tempNetErrorRetries, 94 | authOptions: options.authOptions, 95 | sizeBuf: make([]byte, 4), 96 | dataBuf: make([]byte, defaultInitBuffer), 97 | inFlight: false, 98 | lastUsed: time.Now(), 99 | } 100 | c.initStateData("connCreated", "connTlsStarting", "connActive", "connInactive") 101 | c.setState(connCreated) 102 | return c, nil 103 | } 104 | 105 | func (c *connection) connect() (err error) { 106 | dialer := &net.Dialer{ 107 | Timeout: c.connectTimeout, 108 | KeepAlive: time.Second * 30, 109 | } 110 | c.conn, err = dialer.Dial("tcp", c.addr.String()) // NB: SetNoDelay() is true by default for TCP connections 111 | if err != nil { 112 | logError("[Connection]", "error when dialing %s: '%s'", c.addr.String(), err.Error()) 113 | c.close() 114 | } else { 115 | logDebug("[Connection]", "connected to: %s", c.addr) 116 | if err = c.startTls(); err != nil { 117 | c.close() 118 | c.setState(connInactive) 119 | return 120 | } 121 | c.setState(connActive) 122 | } 123 | return 124 | } 125 | 126 | func (c *connection) startTls() error { 127 | if c.authOptions == nil { 128 | return nil 129 | } 130 | if c.authOptions.TlsConfig == nil { 131 | return ErrAuthMissingConfig 132 | } 133 | c.setState(connTlsStarting) 134 | startTlsCmd := &startTlsCommand{} 135 | if err := c.execute(startTlsCmd); err != nil { 136 | return err 137 | } 138 | var tlsConn *tls.Conn 139 | if tlsConn = tls.Client(c.conn, c.authOptions.TlsConfig); tlsConn == nil { 140 | return ErrAuthTLSUpgradeFailed 141 | } 142 | if err := tlsConn.Handshake(); err != nil { 143 | return err 144 | } 145 | c.conn = tlsConn 146 | authCmd := &authCommand{ 147 | user: c.authOptions.User, 148 | password: c.authOptions.Password, 149 | } 150 | return c.execute(authCmd) 151 | } 152 | 153 | func (c *connection) available() bool { 154 | return (c.conn != nil && c.isStateLessThan(connInactive)) 155 | } 156 | 157 | func (c *connection) close() error { 158 | if c.conn != nil { 159 | err := c.conn.Close() 160 | c.conn = nil 161 | return err 162 | } 163 | return nil 164 | } 165 | 166 | func (c *connection) setInFlight(inFlightVal bool) { 167 | c.inFlight = inFlightVal 168 | } 169 | 170 | func (c *connection) execute(cmd Command) (err error) { 171 | if c.inFlight == true { 172 | err = fmt.Errorf("[Connection] attempted to run '%s' command on in-use connection", cmd.Name()) 173 | return 174 | } 175 | 176 | if lc, ok := cmd.(listingCommand); ok { 177 | allowListing := lc.getAllowListing() 178 | if !allowListing { 179 | err = ErrListingDisabled 180 | cmd.onError(err) 181 | return 182 | } 183 | } 184 | 185 | c.setInFlight(true) 186 | defer c.setInFlight(false) 187 | c.lastUsed = time.Now() 188 | 189 | var message []byte 190 | message, err = getRiakMessage(cmd) 191 | if err != nil { 192 | return 193 | } 194 | 195 | // Use the *greater* of the connection's request timeout 196 | // or the Command's timeout 197 | timeout := c.requestTimeout 198 | if tc, ok := cmd.(timeoutCommand); ok { 199 | tc := tc.getTimeout() 200 | if tc > timeout { 201 | timeout = tc 202 | } 203 | } 204 | 205 | if err = c.write(message, timeout); err != nil { 206 | return 207 | } 208 | 209 | var response []byte 210 | var decoded proto.Message 211 | for { 212 | response, err = c.read(timeout) // NB: response *will* have entire pb message 213 | if err != nil { 214 | cmd.onError(err) 215 | return 216 | } 217 | 218 | // Maybe translate RpbErrorResp into golang error 219 | if err = maybeRiakError(response); err != nil { 220 | cmd.onError(err) 221 | return 222 | } 223 | 224 | if decoded, err = decodeRiakMessage(cmd, response); err != nil { 225 | cmd.onError(err) 226 | return 227 | } 228 | 229 | err = cmd.onSuccess(decoded) 230 | if err != nil { 231 | cmd.onError(err) 232 | return 233 | } 234 | 235 | if sc, ok := cmd.(streamingCommand); ok { 236 | // Streaming Commands indicate done 237 | if sc.isDone() { 238 | return 239 | } 240 | } else { 241 | // non-streaming command, done at this point 242 | return 243 | } 244 | } 245 | } 246 | 247 | func (c *connection) setReadDeadline(t time.Duration) { 248 | c.conn.SetReadDeadline(time.Now().Add(t)) 249 | } 250 | 251 | // NB: This will read one full pb message from Riak, or error in doing so 252 | func (c *connection) read(timeout time.Duration) ([]byte, error) { 253 | if !c.available() { 254 | return nil, ErrCannotRead 255 | } 256 | 257 | var err error 258 | var count int 259 | var messageLength uint32 260 | var rt time.Duration = timeout // rt = 'read timeout' 261 | b := &backoff.Backoff{ 262 | Min: rt, 263 | Jitter: true, 264 | } 265 | try := uint16(0) 266 | 267 | for { 268 | c.setReadDeadline(rt) 269 | if count, err = io.ReadFull(c.conn, c.sizeBuf); err == nil && count == 4 { 270 | messageLength = binary.BigEndian.Uint32(c.sizeBuf) 271 | if messageLength > uint32(cap(c.dataBuf)) { 272 | logDebug("[Connection]", "allocating larger dataBuf of size %d", messageLength) 273 | c.dataBuf = make([]byte, messageLength) 274 | } else { 275 | c.dataBuf = c.dataBuf[0:messageLength] 276 | } 277 | // FUTURE: large object warning / error 278 | // TODO: FUTURE this deadline should subtract the duration taken by the first 279 | // ReadFull call. Currently it's could wait up to 2X the read timout value 280 | c.setReadDeadline(rt) 281 | count, err = io.ReadFull(c.conn, c.dataBuf) 282 | } else { 283 | if err == nil && count != 4 { 284 | err = newClientError(fmt.Sprintf("[Connection] expected to read 4 bytes, only read: %d", count), nil) 285 | } 286 | } 287 | 288 | if err == nil && count != int(messageLength) { 289 | err = newClientError(fmt.Sprintf("[Connection] message length: %d, only read: %d", messageLength, count), nil) 290 | } 291 | 292 | if err == nil { 293 | return c.dataBuf, nil 294 | } 295 | 296 | if try < c.tempNetErrorRetries && isTemporaryNetError(err) { 297 | rt = b.Duration() 298 | try++ 299 | logDebug("[Connection]", "temporary error, re-try %v, new read timeout: %v", try, rt) 300 | } else { 301 | c.setState(connInactive) 302 | return nil, err 303 | } 304 | } 305 | } 306 | 307 | func (c *connection) write(data []byte, timeout time.Duration) error { 308 | if !c.available() { 309 | return ErrCannotWrite 310 | } 311 | c.conn.SetWriteDeadline(time.Now().Add(timeout)) 312 | count, err := c.conn.Write(data) 313 | if err != nil { 314 | c.setState(connInactive) 315 | return err 316 | } 317 | if count != len(data) { 318 | return newClientError(fmt.Sprintf("[Connection] data length: %d, only wrote: %d", len(data), count), nil) 319 | } 320 | return nil 321 | } 322 | -------------------------------------------------------------------------------- /node_i_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package riak 18 | 19 | import ( 20 | "net" 21 | "runtime" 22 | "sync/atomic" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | func TestCreateNodeWithOptionsAndStart(t *testing.T) { 28 | o := &testListenerOpts{ 29 | test: t, 30 | } 31 | tl := newTestListener(o) 32 | tl.start() 33 | defer tl.stop() 34 | 35 | count := uint16(16) 36 | opts := &NodeOptions{ 37 | RemoteAddress: tl.addr.String(), 38 | MinConnections: count, 39 | MaxConnections: count, 40 | IdleTimeout: tenSeconds, 41 | ConnectTimeout: tenSeconds, 42 | RequestTimeout: tenSeconds, 43 | HealthCheckInterval: time.Millisecond * 500, 44 | HealthCheckBuilder: &PingCommandBuilder{}, 45 | TempNetErrorRetries: 128, 46 | } 47 | node, err := NewNode(opts) 48 | if err != nil { 49 | t.Error(err.Error()) 50 | } 51 | if node == nil { 52 | t.Fatal("expected non-nil node") 53 | } 54 | if node.addr.Port != int(tl.port) { 55 | t.Errorf("expected port %d, got: %d", tl.port, node.addr.Port) 56 | } 57 | if node.addr.Zone != "" { 58 | t.Errorf("expected empty zone, got: %s", string(node.addr.Zone)) 59 | } 60 | if expected, actual := opts.MinConnections, node.cm.minConnections; expected != actual { 61 | t.Errorf("expected %v, got: %v", expected, actual) 62 | } 63 | if expected, actual := opts.MaxConnections, node.cm.maxConnections; expected != actual { 64 | t.Errorf("expected %v, got: %v", expected, actual) 65 | } 66 | if expected, actual := opts.IdleTimeout, node.cm.idleTimeout; expected != actual { 67 | t.Errorf("expected %v, got: %v", expected, actual) 68 | } 69 | if err := node.start(); err != nil { 70 | t.Error(err) 71 | } 72 | var f = func(v interface{}) (bool, bool) { 73 | conn := v.(*connection) 74 | if conn == nil { 75 | t.Error("got unexpected nil value") 76 | return true, false 77 | } 78 | if expected, actual := int(tl.port), conn.addr.Port; expected != actual { 79 | t.Errorf("expected %d, got: %d", expected, actual) 80 | } 81 | if conn.addr.Zone != "" { 82 | t.Errorf("expected empty zone, got: %s", string(conn.addr.Zone)) 83 | } 84 | if expected, actual := conn.connectTimeout, opts.ConnectTimeout; expected != actual { 85 | t.Errorf("expected %v, got: %v", expected, actual) 86 | } 87 | if expected, actual := conn.requestTimeout, opts.RequestTimeout; expected != actual { 88 | t.Errorf("expected %v, got: %v", expected, actual) 89 | } 90 | if got, want := conn.tempNetErrorRetries, opts.TempNetErrorRetries; got != want { 91 | t.Errorf("got %v, want %v", got, want) 92 | } 93 | return false, true 94 | } 95 | if err := node.cm.q.iterate(f); err != nil { 96 | t.Error(err) 97 | } 98 | if err := node.stop(); err != nil { 99 | t.Error(err) 100 | } 101 | } 102 | 103 | func TestRecoverViaDefaultPingHealthCheck(t *testing.T) { 104 | connects := uint32(0) 105 | var onConn = func(c net.Conn) bool { 106 | if atomic.AddUint32(&connects, 1) == 1 { 107 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "onConn, closing, connects: %v", connects) 108 | c.Close() 109 | } else { 110 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "onConn, readWriteResp, connects: %v", connects) 111 | readWriteResp(t, c, true) 112 | } 113 | return true 114 | } 115 | o := &testListenerOpts{ 116 | test: t, 117 | onConn: onConn, 118 | } 119 | tl := newTestListener(o) 120 | tl.start() 121 | defer tl.stop() 122 | 123 | doneChan := make(chan struct{}) 124 | stateChan := make(chan state) 125 | 126 | go func() { 127 | opts := &NodeOptions{ 128 | RemoteAddress: tl.addr.String(), 129 | MinConnections: 0, 130 | } 131 | node, err := NewNode(opts) 132 | if err != nil { 133 | t.Error(err) 134 | } 135 | 136 | origSetStateFunc := node.setStateFunc 137 | node.setStateFunc = func(sd *stateData, st state) { 138 | origSetStateFunc(&node.stateData, st) 139 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "sending state '%v' down stateChan", st) 140 | stateChan <- st 141 | } 142 | 143 | node.start() 144 | 145 | pingFunc := func() { 146 | ping := &PingCommand{} 147 | executed, err := node.execute(ping) 148 | if executed == false { 149 | t.Error("expected ping to be executed") 150 | } 151 | if err != nil { 152 | t.Logf("ping err: %v", err.Error()) 153 | } 154 | } 155 | 156 | go func() { 157 | pingFunc() 158 | for { 159 | select { 160 | case <-doneChan: 161 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "stopping pings") 162 | return 163 | case <-time.After(time.Millisecond * 500): 164 | pingFunc() 165 | } 166 | } 167 | }() 168 | 169 | for { 170 | select { 171 | case <-doneChan: 172 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "stopping node") 173 | node.stop() 174 | return 175 | case <-time.After(time.Second * 1): 176 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "still waiting to stop node...") 177 | } 178 | } 179 | }() 180 | 181 | checkStatesFunc := func(states []state) { 182 | idx := 0 183 | for { 184 | select { 185 | case nodeState := <-stateChan: 186 | if expected, actual := states[idx], nodeState; expected != actual { 187 | t.Errorf("expected %v, got %v", expected, actual) 188 | } else { 189 | logDebug("[TestRecoverViaDefaultPingHealthCheck]", "saw state %d", nodeState) 190 | } 191 | idx++ 192 | if idx >= len(states) { 193 | return 194 | } 195 | case <-time.After(time.Second * 5): 196 | buf := make([]byte, 1<<16) 197 | stackSize := runtime.Stack(buf, true) 198 | t.Fatalf("[TestRecoverViaDefaultPingHealthCheck] timeout waiting for stateChan!\n%s", string(buf[0:stackSize])) 199 | return 200 | } 201 | } 202 | } 203 | 204 | expectedStates := []state{ 205 | nodeRunning, nodeHealthChecking, nodeRunning, 206 | } 207 | 208 | checkStatesFunc(expectedStates) 209 | 210 | close(doneChan) 211 | 212 | expectedStates = []state{ 213 | nodeShuttingDown, nodeShutdown, 214 | } 215 | 216 | checkStatesFunc(expectedStates) 217 | 218 | close(stateChan) 219 | } 220 | 221 | func TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck(t *testing.T) { 222 | o := &testListenerOpts{ 223 | test: t, 224 | host: "127.0.0.1", 225 | port: 13338, 226 | } 227 | tl := newTestListener(o) 228 | defer tl.stop() 229 | 230 | stateChan := make(chan state) 231 | recoveredChan := make(chan struct{}) 232 | 233 | var err error 234 | var node *Node 235 | 236 | go func() { 237 | listenerStarted := false 238 | nodeIsRunningCount := 0 239 | for { 240 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "waiting on stateChan...") 241 | if nodeState, ok := <-stateChan; ok { 242 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "received nodeState: '%v'", nodeState) 243 | if nodeState == nodeRunning { 244 | nodeIsRunningCount++ 245 | } 246 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "nodeIsRunningCount: '%v'", nodeIsRunningCount) 247 | if nodeIsRunningCount == 2 { 248 | // This is the second time node has entered nodeRunning state, so it must have recovered via the healthcheck 249 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "SUCCESS node recovered via healthcheck") 250 | close(recoveredChan) 251 | break 252 | } 253 | if !listenerStarted && nodeState == nodeHealthChecking { 254 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "STARTING LISTENER") 255 | tl.start() 256 | listenerStarted = true 257 | } 258 | } else { 259 | t.Error("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck] stateChan closed before recovering via healthcheck") 260 | break 261 | } 262 | } 263 | }() 264 | 265 | opts := &NodeOptions{ 266 | ConnectTimeout: 500 * time.Millisecond, 267 | RemoteAddress: "127.0.0.1:13338", // NB: can't use tl.addr since it isn't set until tl.start() 268 | } 269 | node, err = NewNode(opts) 270 | if err != nil { 271 | t.Fatal(err) 272 | } 273 | origSetStateFunc := node.setStateFunc 274 | 275 | go func() { 276 | node.setStateFunc = func(sd *stateData, st state) { 277 | origSetStateFunc(&node.stateData, st) 278 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "SENDING state '%v' down stateChan", st) 279 | stateChan <- st 280 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "SENT state '%v' down stateChan", st) 281 | } 282 | node.start() 283 | 284 | pingFunc := func() { 285 | ping := &PingCommand{} 286 | if _, perr := node.execute(ping); perr != nil { 287 | t.Logf("ping err: %v", perr) 288 | } 289 | } 290 | 291 | pingFunc() 292 | for { 293 | select { 294 | case <-recoveredChan: 295 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "stopping pings") 296 | return 297 | case <-time.After(time.Millisecond * 500): 298 | pingFunc() 299 | } 300 | } 301 | }() 302 | 303 | select { 304 | case <-recoveredChan: 305 | logDebug("[TestRecoverAfterConnectionComesUpViaDefaultPingHealthCheck]", "recovered") 306 | node.setStateFunc = origSetStateFunc 307 | node.stop() 308 | close(stateChan) 309 | case <-time.After(10 * time.Second): 310 | t.Error("test timed out") 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-present Basho Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package riak 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "time" 21 | ) 22 | 23 | // Constants identifying Node state 24 | const ( 25 | nodeCreated state = iota 26 | nodeRunning 27 | nodeHealthChecking 28 | nodeShuttingDown 29 | nodeShutdown 30 | nodeError 31 | ) 32 | 33 | // NodeOptions defines the RemoteAddress and operational configuration for connections to a Riak KV 34 | // instance 35 | type NodeOptions struct { 36 | RemoteAddress string 37 | MinConnections uint16 38 | MaxConnections uint16 39 | TempNetErrorRetries uint16 40 | IdleTimeout time.Duration 41 | ConnectTimeout time.Duration 42 | RequestTimeout time.Duration 43 | HealthCheckInterval time.Duration 44 | HealthCheckBuilder CommandBuilder 45 | AuthOptions *AuthOptions 46 | } 47 | 48 | // Node is a struct that contains all of the information needed to connect and maintain connections 49 | // with a Riak KV instance 50 | type Node struct { 51 | addr *net.TCPAddr 52 | healthCheckInterval time.Duration 53 | healthCheckBuilder CommandBuilder 54 | stopChan chan struct{} 55 | cm *connectionManager 56 | stateData 57 | } 58 | 59 | var defaultNodeOptions = &NodeOptions{ 60 | RemoteAddress: defaultRemoteAddress, 61 | MinConnections: defaultMinConnections, 62 | MaxConnections: defaultMaxConnections, 63 | TempNetErrorRetries: defaultTempNetErrorRetries, 64 | IdleTimeout: defaultIdleTimeout, 65 | ConnectTimeout: defaultConnectTimeout, 66 | RequestTimeout: defaultRequestTimeout, 67 | } 68 | 69 | // NewNode is a factory function that takes a NodeOptions struct and returns a Node struct 70 | func NewNode(options *NodeOptions) (*Node, error) { 71 | if options == nil { 72 | options = defaultNodeOptions 73 | } 74 | if options.RemoteAddress == "" { 75 | options.RemoteAddress = defaultRemoteAddress 76 | } 77 | if options.MinConnections == 0 { 78 | options.MinConnections = defaultMinConnections 79 | } 80 | if options.MaxConnections == 0 { 81 | options.MaxConnections = defaultMaxConnections 82 | } 83 | if options.TempNetErrorRetries == 0 { 84 | options.TempNetErrorRetries = defaultTempNetErrorRetries 85 | } 86 | if options.IdleTimeout == 0 { 87 | options.IdleTimeout = defaultIdleTimeout 88 | } 89 | if options.ConnectTimeout == 0 { 90 | options.ConnectTimeout = defaultConnectTimeout 91 | } 92 | if options.RequestTimeout == 0 { 93 | options.RequestTimeout = defaultRequestTimeout 94 | } 95 | if options.HealthCheckInterval == 0 { 96 | options.HealthCheckInterval = defaultHealthCheckInterval 97 | } 98 | 99 | var err error 100 | var resolvedAddress *net.TCPAddr 101 | resolvedAddress, err = net.ResolveTCPAddr("tcp", options.RemoteAddress) 102 | if err == nil { 103 | n := &Node{ 104 | stopChan: make(chan struct{}), 105 | addr: resolvedAddress, 106 | healthCheckInterval: options.HealthCheckInterval, 107 | healthCheckBuilder: options.HealthCheckBuilder, 108 | } 109 | 110 | connMgrOpts := &connectionManagerOptions{ 111 | addr: resolvedAddress, 112 | minConnections: options.MinConnections, 113 | maxConnections: options.MaxConnections, 114 | tempNetErrorRetries: options.TempNetErrorRetries, 115 | idleTimeout: options.IdleTimeout, 116 | connectTimeout: options.ConnectTimeout, 117 | requestTimeout: options.RequestTimeout, 118 | authOptions: options.AuthOptions, 119 | } 120 | 121 | var cm *connectionManager 122 | if cm, err = newConnectionManager(connMgrOpts); err == nil { 123 | n.cm = cm 124 | n.initStateData("nodeCreated", "nodeRunning", "nodeHealthChecking", "nodeShuttingDown", "nodeShutdown", "nodeError") 125 | n.setState(nodeCreated) 126 | return n, nil 127 | } 128 | } 129 | 130 | return nil, err 131 | } 132 | 133 | // String returns a formatted string including the remoteAddress for the Node and its current 134 | // connection count 135 | func (n *Node) String() string { 136 | return fmt.Sprintf("%v|%d|%d", n.addr, n.cm.count(), n.cm.q.count()) 137 | } 138 | 139 | // Start opens a connection with Riak at the configured remoteAddress and adds the connections to the 140 | // active pool 141 | func (n *Node) start() error { 142 | if err := n.stateCheck(nodeCreated); err != nil { 143 | return err 144 | } 145 | 146 | logDebug("[Node]", "(%v) starting", n) 147 | if err := n.cm.start(); err != nil { 148 | logErr("[Node]", err) 149 | } 150 | n.setState(nodeRunning) 151 | logDebug("[Node]", "(%v) started", n) 152 | 153 | return nil 154 | } 155 | 156 | // Stop closes the connections with Riak at the configured remoteAddress and removes the connections 157 | // from the active pool 158 | func (n *Node) stop() error { 159 | if err := n.stateCheck(nodeRunning, nodeHealthChecking); err != nil { 160 | return err 161 | } 162 | 163 | logDebug("[Node]", "(%v) shutting down.", n) 164 | 165 | n.setState(nodeShuttingDown) 166 | close(n.stopChan) 167 | 168 | err := n.cm.stop() 169 | 170 | if err == nil { 171 | n.setState(nodeShutdown) 172 | logDebug("[Node]", "(%v) shut down.", n) 173 | } else { 174 | n.setState(nodeError) 175 | logErr("[Node]", err) 176 | } 177 | 178 | return err 179 | } 180 | 181 | // Execute retrieves an available connection from the pool and executes the Command operation against 182 | // Riak 183 | func (n *Node) execute(cmd Command) (bool, error) { 184 | if err := n.stateCheck(nodeRunning, nodeHealthChecking); err != nil { 185 | return false, err 186 | } 187 | 188 | if n.isCurrentState(nodeRunning) { 189 | conn, err := n.cm.get() 190 | if err != nil { 191 | logErr("[Node]", err) 192 | n.doHealthCheck() 193 | return false, err 194 | } 195 | 196 | if conn == nil { 197 | panic(fmt.Sprintf("[Node] (%v) expected non-nil connection", n)) 198 | } 199 | 200 | if rc, ok := cmd.(retryableCommand); ok { 201 | rc.setLastNode(n) 202 | } 203 | 204 | logDebug("[Node]", "(%v) - executing command '%v'", n, cmd.Name()) 205 | err = conn.execute(cmd) 206 | if err == nil { 207 | // NB: basically the success path of _responseReceived in Node.js client 208 | if cmErr := n.cm.put(conn); cmErr != nil { 209 | logErr("[Node]", cmErr) 210 | } 211 | return true, nil 212 | } else { 213 | // NB: basically, this is _connectionClosed / _responseReceived in Node.js client 214 | // must differentiate between Riak and non-Riak errors here and within execute() in connection 215 | switch err.(type) { 216 | case RiakError, ClientError: 217 | // Riak and Client errors will not close connection 218 | if cmErr := n.cm.put(conn); cmErr != nil { 219 | logErr("[Node]", cmErr) 220 | } 221 | return true, err 222 | default: 223 | // NB: must be a non-Riak, non-Client error, close the connection 224 | if cmErr := n.cm.remove(conn); cmErr != nil { 225 | logErr("[Node]", cmErr) 226 | } 227 | if !isTemporaryNetError(err) { 228 | n.doHealthCheck() 229 | } 230 | return true, err 231 | } 232 | } 233 | } else { 234 | return false, nil 235 | } 236 | } 237 | 238 | func (n *Node) doHealthCheck() { 239 | // NB: ensure we're not already healthchecking or shutting down 240 | if n.isStateLessThan(nodeHealthChecking) { 241 | n.setState(nodeHealthChecking) 242 | go n.healthCheck() 243 | } else { 244 | logDebug("[Node]", "(%v) is already healthchecking or shutting down.", n) 245 | } 246 | } 247 | 248 | func (n *Node) getHealthCheckCommand() (hc Command) { 249 | // This is necessary to have a unique Command struct as part of each 250 | // connection so that concurrent calls to check health can all have 251 | // unique results 252 | var err error 253 | if n.healthCheckBuilder != nil { 254 | hc, err = n.healthCheckBuilder.Build() 255 | } else { 256 | hc = &PingCommand{} 257 | } 258 | 259 | if err != nil { 260 | logErr("[Node]", err) 261 | hc = &PingCommand{} 262 | } 263 | 264 | return 265 | } 266 | 267 | func (n *Node) ensureHealthCheckCanContinue() bool { 268 | // ensure we ARE healthchecking 269 | if !n.isCurrentState(nodeHealthChecking) { 270 | logDebug("[Node]", "(%v) expected healthchecking state, got %s", n, n.stateData.String()) 271 | return false 272 | } 273 | return true 274 | } 275 | 276 | // private goroutine funcs 277 | 278 | func (n *Node) healthCheck() { 279 | logDebug("[Node]", "(%v) starting healthcheck routine", n) 280 | 281 | healthCheckTicker := time.NewTicker(n.healthCheckInterval) 282 | defer healthCheckTicker.Stop() 283 | 284 | for { 285 | if !n.ensureHealthCheckCanContinue() { 286 | return 287 | } 288 | select { 289 | case <-n.stopChan: 290 | logDebug("[Node]", "(%v) healthcheck quitting", n) 291 | return 292 | case t := <-healthCheckTicker.C: 293 | if !n.ensureHealthCheckCanContinue() { 294 | return 295 | } 296 | logDebug("[Node]", "(%v) running healthcheck at %v", n, t) 297 | conn, cerr := n.cm.createConnection() 298 | if cerr != nil { 299 | conn.close() 300 | logError("[Node]", "(%v) failed healthcheck in createConnection, err: %v", n, cerr) 301 | } else { 302 | if !n.ensureHealthCheckCanContinue() { 303 | conn.close() 304 | return 305 | } 306 | hcmd := n.getHealthCheckCommand() 307 | logDebug("[Node]", "(%v) healthcheck executing %v", n, hcmd.Name()) 308 | if hcerr := conn.execute(hcmd); hcerr != nil || !hcmd.Success() { 309 | conn.close() 310 | logError("[Node]", "(%v) failed healthcheck, err: %v", n, hcerr) 311 | } else { 312 | conn.close() 313 | logDebug("[Node]", "(%v) healthcheck success, err: %v, success: %v", n, hcerr, hcmd.Success()) 314 | if n.ensureHealthCheckCanContinue() { 315 | n.setState(nodeRunning) 316 | } 317 | return 318 | } 319 | } 320 | } 321 | } 322 | } 323 | --------------------------------------------------------------------------------