logstash-output-redisearch 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 60a5ad2c1a084053f9275022336e2e32620c08ff5a44041743e7a3b4860bf063
4
+ data.tar.gz: 8769d6da6888847da02eab1ef5972d0015a0567ec4246b73d1380fb58039fbac
5
+ SHA512:
6
+ metadata.gz: df4d0505f0e869b38fcb74de6749ff86e81a483a57d9eab620515bd1494277ede9b6115ca1a3adaafaaa07f46d8aae4191c0253e2a732974b29446da98b2ad21
7
+ data.tar.gz: d43c3d54cf4363fb1a4a25a102a4a21f55b1b6d83ec94b80d4f567bdfc676269b3ef351f717212f942e0749e0aee6f050c11a84bb1ac8c87bfe930c5ee944e46
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.1.0
2
+ - Plugin created with the logstash plugin generator
data/CONTRIBUTORS ADDED
@@ -0,0 +1,10 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * - HashedIn
6
+
7
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
8
+ Logstash, and you aren't on the list above and want to be, please let us know
9
+ and we'll make sure you're here. Contributions from folks like you are what make
10
+ open source awesome.
data/DEVELOPER.md ADDED
@@ -0,0 +1,2 @@
1
+ # logstash-output-redisearch
2
+ Example output plugin. This should help bootstrap your effort to write your own output plugin!
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # Logstash Output Plugin
2
+
3
+ This logstash output plugin is for Redisearch.
4
+ - Note: Plugin has not been tested with cluster mode.
5
+
6
+ ### 1. Plugin Development and Testing
7
+
8
+ #### Requirements
9
+ * JRuby (Use Ruby Version Manger(RVM))
10
+ * JDK
11
+ * Git
12
+ * bundler
13
+ * Redisearch
14
+ * Logstash
15
+
16
+ #### Install requirements
17
+ * RVM
18
+ ```bash
19
+ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
20
+ \curl -sSL https://get.rvm.io | bash
21
+ source ~/.rvm/scripts/rvm
22
+ ```
23
+ * JRuby
24
+ ```bash
25
+ rvm install jruby
26
+ ```
27
+
28
+ * JDK
29
+ ```bash
30
+ sudo apt install default-jdk
31
+ echo "export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac) )))" >> ~/.profile
32
+ source ~/.profile
33
+ ```
34
+
35
+ * bundler
36
+ ```bash
37
+ gem install bundler
38
+ ```
39
+
40
+ * Redisearch
41
+ ```bash
42
+ git clone --recursive https://github.com/RediSearch/RediSearch.git
43
+ make build
44
+ make run
45
+ ```
46
+
47
+ #### Code
48
+
49
+ - Clone Project
50
+ ```bash
51
+ git clone https://github.com/hashedin/logstash-output-redisearch.git
52
+ ```
53
+ - Use JRuby
54
+ ```bash
55
+ rvm use jruby
56
+ ```
57
+
58
+ - Install dependencies
59
+ ```bash
60
+ bundle install
61
+ ```
62
+
63
+ #### Test
64
+
65
+ - Run tests
66
+
67
+ ```bash
68
+ bundle exec rspec
69
+ ```
70
+
71
+ ### 2. Running your Plugin in Logstash
72
+
73
+ * Install Logstash
74
+ ```bash
75
+ wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
76
+ sudo apt-get install apt-transport-https
77
+ echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list
78
+ sudo apt-get update && sudo apt-get install logstash
79
+ sudo /usr/share/logstash/bin/system-install /etc/logstash/startup.options systemd
80
+ ```
81
+
82
+ #### Run in a local Logstash
83
+
84
+ - Build Gemfile
85
+
86
+ ```bash
87
+ gem build logstash-output-redisearch.gemspec
88
+ ```
89
+
90
+ - Deploy Gemfile to Logstash
91
+
92
+ ```bash
93
+ bin/logstash-plugin install /path/to/logstash-output-redisearch-0.1.0.gem
94
+ ```
95
+
96
+ - Verify installed plugin
97
+ ```bash
98
+ bin/logstash-plugin list
99
+ There should be logstash-output-redisearch
100
+ ```
101
+ #### Start logstash output plugin
102
+
103
+ - Configuration options
104
+
105
+ | Name | Description | Type | Default |
106
+ | --- | --- | --- | --- |
107
+ | host | Redis-server IP address | string | "127.0.0.1" |
108
+ | port | Redis-server port number | number | 6379 |
109
+ | index | Name an index in redisearch | string | "logstash-current-date" |
110
+ | batch_events | Max number of events in a buffer before flush | number | 50 |
111
+ | batch_timeout | Max interval to pass before flush | number | 5 |
112
+ | ssl | SSL authentication | boolean | false |
113
+ | password | Password for authentication | password | - |
114
+
115
+ * Usage
116
+ ```bash
117
+ output {
118
+ redisearch {
119
+ }
120
+ }
121
+ ```
122
+ OR
123
+
124
+ ```bash
125
+ output {
126
+ redisearch {
127
+ host => '192.168.0.1'
128
+ port => 6379
129
+ index => logstash
130
+ batch_events => 20
131
+ batch_timeout => 2
132
+ ssl => true
133
+ password => "123"
134
+ }
135
+ }
136
+ ```
137
+
138
+ #### Example
139
+
140
+ Let's create a logstash pipleline using filebeat as input plugin and redisearch as output plugin:
141
+ 1. Install filebeat and configure /etc/filebeat/filebeat.yml as following:
142
+ - Install filebeat:
143
+ ```bash
144
+ sudo apt-get install filebeat
145
+ ```
146
+ - Enable filebeat input to read from file:
147
+ ```bash
148
+ filebeat.inputs:
149
+ enabled: true
150
+ paths:
151
+ - /path/to/logfile
152
+ ```
153
+
154
+ - Change filebeat output from elasticsearch to logstash:
155
+ ```bash
156
+ output.logstash:
157
+ hosts: [“localhost:5044”]
158
+ ```
159
+
160
+ 2. Create a conf file in /etc/logstash/conf.d
161
+ ```bash
162
+ input {
163
+ beats {
164
+ Port => 5044
165
+ }
166
+ output {
167
+ redisearch {
168
+ }
169
+ }
170
+ ```
171
+
172
+ 3. After configuring, restart logstash and filebeat services and check the data stashing into redisearch.
173
+ ```bash
174
+ sudo service logstash restart
175
+ sudo service filebeat restart
176
+ ```
177
+
178
+ #### References
179
+
180
+ * https://github.com/logstash-plugins/logstash-output-redis : Redis Output Plugin
181
+ * https://github.com/logstash-plugins/logstash-output-elasticsearch : Elasticsearch Output Plugin
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+ # require "logstash/outputs/template"
3
+ require "logstash/outputs/template"
4
+ require "logstash/outputs/base"
5
+ require 'redisearch-rb'
6
+ require "stud/buffer"
7
+ require 'json'
8
+ require 'time'
9
+
10
+ # An redisearch output will store data into Redisearch.
11
+ class LogStash::Outputs::Redisearch < LogStash::Outputs::Base
12
+ include Stud::Buffer
13
+ config_name "redisearch"
14
+ default :codec, "json"
15
+
16
+ # Hostname of rediserver to connect to rediserver.
17
+ config :host, :validate => :string, :default => "127.0.0.1"
18
+
19
+ # Port number to connect to rediserver
20
+ config :port, :validate => :number, :default => 6379
21
+
22
+ # Index name to create or to connect the existing old spec
23
+ config :index, :validate => :string, :default => nil
24
+
25
+ # Interval to reconnect if Failure in redis connection
26
+ config :reconnect_interval, :validate => :number, :default => 1
27
+
28
+ # Max number of events to add in a list
29
+ config :batch_events, :validate => :number, :default => 50
30
+
31
+ # Max interval to pass before flush
32
+ config :batch_timeout, :validate => :number, :default => 5
33
+
34
+ # SSL flag for aunthentication
35
+ config :ssl, :validate => :boolean, :default => false
36
+
37
+ # Password for aunthentication
38
+ config :password, :validate => :password
39
+
40
+ # Method is a constructor to this class.
41
+ # Used to intialize buffer, redisearch client and also to create a index if it is not present
42
+ public
43
+ def register
44
+
45
+ buffer_initialize(
46
+ :max_items => @batch_events,
47
+ :max_interval => @batch_timeout,
48
+ )
49
+
50
+ params = {
51
+ "host"=>@host,
52
+ "port"=>@port,
53
+ "index"=>@index,
54
+ "ssl"=>@ssl
55
+ }
56
+
57
+ if @password
58
+ params["password"] = @password.value
59
+ end
60
+
61
+ @idx = Index.new(params)
62
+ @redisearch_client = @idx.connect()
63
+ @codec.on_event(&method(:send_to_redisearch))
64
+
65
+ end # def register
66
+
67
+ # Method is to receive event and encode it in json format.
68
+ public
69
+ def receive(event)
70
+ begin
71
+ @codec.encode(event)
72
+ rescue StandardError => e
73
+ @logger.warn("Error encoding event", :exception => e,
74
+ :event => event)
75
+ sleep @reconnect_interval
76
+ retry
77
+ end
78
+ end # def event
79
+
80
+ # Method is called from Stud::Buffer when max_items/max_interval is reached
81
+ def flush(events, close=false)
82
+ #buffer_flush should pass here the :final boolean value.
83
+ @redisearch_client.add_docs(events)
84
+ @logger.info("Buffer Inserted Successfully", :length => events.length)
85
+ end
86
+
87
+ # Method is called from Stud::Buffer when an error occurs
88
+ def on_flush_error(e)
89
+ @logger.warn("Failed to send backlog of events to Redisearch",
90
+ :exception => e,
91
+ :backtrace => e.backtrace
92
+ )
93
+ end
94
+
95
+ # Method is for final bookkeeping and cleanup when plugin thread exit
96
+ def close
97
+ # Force full flush call to ensure that all accumulated messages are flushed.
98
+ buffer_flush(:final => true)
99
+ end
100
+
101
+ # Method to assign uuid to each event (formatting event as per document required by redisearch)
102
+ # and to append each event to buffer
103
+ def send_to_redisearch(event, payload)
104
+ begin
105
+ doc_data = JSON.parse(payload)
106
+ doc_id = @idx.get_id()
107
+ document = [doc_id,doc_data]
108
+ buffer_receive(document)
109
+
110
+ rescue => e
111
+ @logger.warn("Failed to send event to Redisearch", :event => event,
112
+ :exception => e,
113
+ :backtrace => e.backtrace)
114
+ sleep @reconnect_interval
115
+ retry
116
+ end
117
+ end
118
+ end # class LogStash::Outputs::Redisearch
@@ -0,0 +1,9 @@
1
+ {
2
+ "index": "logstash-*",
3
+ "schema": {
4
+ "@timestamp": ["TEXT", "SORTABLE"],
5
+ "host" : "TEXT",
6
+ "@version": "TEXT",
7
+ "message": "TEXT"
8
+ }
9
+ }
@@ -0,0 +1,65 @@
1
+ require 'json'
2
+ require 'redisearch-rb'
3
+ require 'time'
4
+ require 'redis'
5
+ require 'securerandom'
6
+ require 'base64'
7
+
8
+ # Redisearch index management
9
+ class Index
10
+ # initialize and create redis instance using params value
11
+ def initialize(params)
12
+ begin
13
+
14
+ @template_data = read_template()
15
+ @redis = Redis.new(
16
+ host: params["host"],
17
+ port: params["port"],
18
+ ssl: params["ssl"],
19
+ password:params["password"])
20
+
21
+ if params["index"] == nil
22
+ time = Time.new
23
+ @idx_name = @template_data['index'].sub! '*', time.strftime("%Y-%m-%d")
24
+ else
25
+ @idx_name = params["index"]
26
+ end
27
+
28
+ rescue => e
29
+ @logger.debug("Exception in Index initialization",e)
30
+ end
31
+ end
32
+
33
+
34
+ # Reads json file and returns data
35
+ def read_template()
36
+ begin
37
+ filepath = ::File.expand_path('template.json', ::File.dirname(__FILE__))
38
+ file_data = ::IO.read(filepath)
39
+ data = JSON.load(file_data)
40
+ rescue => e
41
+ @logger.debug("Exception in reading template", e)
42
+ end
43
+ return data
44
+ end
45
+
46
+ # Creates redisearch instance using redis instance.
47
+ # Using redisearch instance, connects to index if it is present, else creates a new index
48
+ def connect()
49
+ begin
50
+ @rs = RediSearch.new(@idx_name,@redis)
51
+ @rs.info()
52
+ rescue
53
+ @schema = @template_data['schema']
54
+ @rs.create_index(@schema)
55
+ end
56
+ return @rs
57
+ end
58
+
59
+ # Id for each document in redisearch
60
+ def get_id()
61
+ uuid = SecureRandom.uuid
62
+ id = Base64.encode64([ uuid.tr('-', '') ].pack('H*')).gsub(/\=*\n/, '')
63
+ return id
64
+ end
65
+ end #Class Index
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-output-redisearch'
3
+ s.version = '0.1.1'
4
+ s.licenses = ['Apache-2.0']
5
+ s.summary = 'Stash logs in Redisearch'
6
+ s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
7
+ s.homepage = 'https://github.com/hashedin/logstash-output-redisearch'
8
+ s.authors = ['HashedIn Technologies']
9
+ s.email = 'redis-connectors@hashedin.com'
10
+ s.require_paths = ['lib']
11
+
12
+ # Files
13
+ s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
14
+ # Tests
15
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
+
17
+ # Special flag to let us know this is actually a logstash plugin
18
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
19
+
20
+ # Gem dependencies
21
+ s.add_runtime_dependency "logstash-core-plugin-api", "~> 2.0"
22
+ s.add_runtime_dependency "logstash-codec-plain", '~> 3.0'
23
+ s.add_runtime_dependency 'logstash-codec-json', '~> 3.0'
24
+ s.add_runtime_dependency 'redis', '~> 4'
25
+ s.add_runtime_dependency 'redisearch-rb', '~> 1'
26
+ s.add_development_dependency 'logstash-devutils'
27
+ s.add_development_dependency 'flores'
28
+
29
+ end
@@ -0,0 +1,28 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/redisearch"
3
+ require "redisearch"
4
+
5
+ describe LogStash::Outputs::Redisearch do
6
+
7
+ context "batch of events" do
8
+
9
+ let(:config) {
10
+ {
11
+ "batch_events" => 10,
12
+ "batch_timeout" => 1,
13
+ }
14
+ }
15
+ let(:redisearch) { described_class.new(config) }
16
+
17
+ it "should call buffer_receive" do
18
+ redisearch.register
19
+ expect(redisearch).to receive(:buffer_receive).exactly(100).times.and_call_original
20
+ expect(redisearch).to receive(:flush).exactly(10).times
21
+ expect(redisearch).not_to receive(:on_flush_error)
22
+
23
+ 100.times do |i|
24
+ expect{redisearch.receive(LogStash::Event.new({"message" => "test-#{i}"}))}.to_not raise_error
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/redisearch"
4
+ require "logstash/event"
5
+ require "redisearch-rb"
6
+ require "flores/random"
7
+ require "redis"
8
+
9
+ describe LogStash::Outputs::Redisearch do
10
+ context "to check if events are inserted to redisearch" do
11
+ subject { described_class.new(config) }
12
+
13
+ options = {
14
+ "host" => "127.0.0.1",
15
+ "port" => 6379,
16
+ "index" => "test_idx",
17
+ }
18
+
19
+ let(:config) {
20
+ options
21
+ }
22
+ let(:event_count) { Flores::Random.integer(0..1000) }
23
+ let(:message) { Flores::Random.text(0..2) }
24
+ redis = Redis.new(host: options["host"], port: options["port"])
25
+ rs = RediSearch.new(options["index"],redis)
26
+
27
+ before do
28
+ subject.register
29
+ event_count.times do |i|
30
+ event = LogStash::Event.new("sequence" => i, "message" => message)
31
+ subject.receive(event)
32
+ end
33
+ subject.close
34
+ end
35
+
36
+ it "search for a text in redisearch" do
37
+ info = rs.search("message")
38
+ insist { info.count } == 0
39
+ end
40
+
41
+ it "count number of docs in redisearch are same as number of events" do
42
+ info = rs.info()
43
+ insist { info['num_docs'].to_i } == event_count
44
+ end
45
+
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-redisearch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - HashedIn Technologies
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ name: logstash-core-plugin-api
20
+ prerelease: false
21
+ type: :runtime
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ name: logstash-codec-plain
34
+ prerelease: false
35
+ type: :runtime
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ name: logstash-codec-json
48
+ prerelease: false
49
+ type: :runtime
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4'
61
+ name: redis
62
+ prerelease: false
63
+ type: :runtime
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1'
75
+ name: redisearch-rb
76
+ prerelease: false
77
+ type: :runtime
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1'
83
+ - !ruby/object:Gem::Dependency
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ name: logstash-devutils
90
+ prerelease: false
91
+ type: :development
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ name: flores
104
+ prerelease: false
105
+ type: :development
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: This gem is a Logstash plugin required to be installed on top of the
112
+ Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This
113
+ gem is not a stand-alone program
114
+ email: redis-connectors@hashedin.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - CHANGELOG.md
120
+ - CONTRIBUTORS
121
+ - DEVELOPER.md
122
+ - Gemfile
123
+ - LICENSE
124
+ - README.md
125
+ - lib/logstash/outputs/redisearch.rb
126
+ - lib/logstash/outputs/template.json
127
+ - lib/logstash/outputs/template.rb
128
+ - logstash-output-redisearch.gemspec
129
+ - spec/outputs/batch_spec.rb
130
+ - spec/outputs/redisearch_spec.rb
131
+ homepage: https://github.com/hashedin/logstash-output-redisearch
132
+ licenses:
133
+ - Apache-2.0
134
+ metadata:
135
+ logstash_plugin: 'true'
136
+ logstash_group: output
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.7.10
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Stash logs in Redisearch
157
+ test_files:
158
+ - spec/outputs/batch_spec.rb
159
+ - spec/outputs/redisearch_spec.rb