efigence-influxdb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rubocop.yml +41 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +362 -0
  8. data/Rakefile +22 -0
  9. data/efigence-influxdb.gemspec +28 -0
  10. data/lib/influxdb.rb +21 -0
  11. data/lib/influxdb/client.rb +77 -0
  12. data/lib/influxdb/client/http.rb +98 -0
  13. data/lib/influxdb/config.rb +60 -0
  14. data/lib/influxdb/errors.rb +40 -0
  15. data/lib/influxdb/logging.rb +22 -0
  16. data/lib/influxdb/max_queue.rb +18 -0
  17. data/lib/influxdb/point_value.rb +31 -0
  18. data/lib/influxdb/query/cluster.rb +17 -0
  19. data/lib/influxdb/query/continuous_query.rb +36 -0
  20. data/lib/influxdb/query/core.rb +109 -0
  21. data/lib/influxdb/query/database.rb +21 -0
  22. data/lib/influxdb/query/series.rb +13 -0
  23. data/lib/influxdb/query/shard.rb +14 -0
  24. data/lib/influxdb/query/shard_space.rb +60 -0
  25. data/lib/influxdb/query/user.rb +38 -0
  26. data/lib/influxdb/version.rb +3 -0
  27. data/lib/influxdb/writer/async.rb +115 -0
  28. data/lib/influxdb/writer/udp.rb +21 -0
  29. data/spec/influxdb/cases/async_client_spec.rb +33 -0
  30. data/spec/influxdb/cases/query_cluster_spec.rb +65 -0
  31. data/spec/influxdb/cases/query_database_spec.rb +58 -0
  32. data/spec/influxdb/cases/query_series_spec.rb +50 -0
  33. data/spec/influxdb/cases/query_shard_spec.rb +43 -0
  34. data/spec/influxdb/cases/query_user_spec.rb +127 -0
  35. data/spec/influxdb/cases/querying_spec.rb +159 -0
  36. data/spec/influxdb/cases/retry_requests_spec.rb +97 -0
  37. data/spec/influxdb/cases/udp_client_spec.rb +21 -0
  38. data/spec/influxdb/cases/write_points_spec.rb +141 -0
  39. data/spec/influxdb/client_spec.rb +58 -0
  40. data/spec/influxdb/config_spec.rb +118 -0
  41. data/spec/influxdb/logging_spec.rb +48 -0
  42. data/spec/influxdb/max_queue_spec.rb +29 -0
  43. data/spec/influxdb/point_value_spec.rb +66 -0
  44. data/spec/influxdb/worker_spec.rb +23 -0
  45. data/spec/spec_helper.rb +8 -0
  46. metadata +192 -0
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe InfluxDB::Client do
5
+ let(:subject) do
6
+ described_class.new(
7
+ "database",
8
+ {
9
+ host: "influxdb.test",
10
+ port: 9999,
11
+ username: "username",
12
+ password: "password",
13
+ time_precision: "s"
14
+ }.merge(args)
15
+ )
16
+ end
17
+
18
+ let(:args) { {} }
19
+
20
+ describe "#create_cluster_admin" do
21
+ let(:user) { 'adminadmin' }
22
+ let(:pass) { 'passpass' }
23
+ let(:query) { "CREATE USER #{user} WITH PASSWORD '#{pass}' WITH ALL PRIVILEGES" }
24
+
25
+ before do
26
+ stub_request(:get, "http://influxdb.test:9999/query").with(
27
+ query: {u: "username", p: "password", q: query}
28
+ )
29
+ end
30
+
31
+ it "should GET to create a new cluster admin" do
32
+ expect(subject.create_cluster_admin(user, pass)).to be_a(Net::HTTPOK)
33
+ end
34
+ end
35
+
36
+ describe "#list_cluster_admins" do
37
+ let(:response) { {"results"=>[{"series"=>[{"columns"=>["user", "admin"], "values"=>[["dbadmin", true], ["foobar", false]]}]}]} }
38
+ let(:expected_result) { [{"username"=>"dbadmin"}] }
39
+
40
+ before do
41
+ stub_request(:get, "http://influxdb.test:9999/query").with(
42
+ query: {u: "username", p: "password", q: "SHOW USERS"}
43
+ ).to_return(:body => JSON.generate(response, :status => 200))
44
+ end
45
+
46
+ it "should GET a list of cluster admins" do
47
+ expect(subject.list_cluster_admins).to eq(expected_result)
48
+ end
49
+ end
50
+
51
+ describe "#revoke_cluster_admin_privileges" do
52
+ let(:user) { 'useruser' }
53
+ let(:query) { "REVOKE ALL PRIVILEGES FROM #{user}" }
54
+
55
+ before do
56
+ stub_request(:get, "http://influxdb.test:9999/query").with(
57
+ query: {u: "username", p: "password", q: query}
58
+ )
59
+ end
60
+
61
+ it "should GET to revoke cluster admin privileges from a user" do
62
+ expect(subject.revoke_cluster_admin_privileges(user)).to be_a(Net::HTTPOK)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe InfluxDB::Client do
5
+ let(:subject) do
6
+ described_class.new(
7
+ "database",
8
+ {
9
+ host: "influxdb.test",
10
+ port: 9999,
11
+ username: "username",
12
+ password: "password",
13
+ time_precision: "s"
14
+ }.merge(args)
15
+ )
16
+ end
17
+
18
+ let(:args) { {} }
19
+
20
+ describe "#create_database" do
21
+ before do
22
+ stub_request(:get, "http://influxdb.test:9999/query").with(
23
+ query: {u: "username", p: "password", q: "CREATE DATABASE foo"}
24
+ )
25
+ end
26
+
27
+ it "should GET to create a new database" do
28
+ expect(subject.create_database("foo")).to be_a(Net::HTTPOK)
29
+ end
30
+ end
31
+
32
+ describe "#delete_database" do
33
+ before do
34
+ stub_request(:get, "http://influxdb.test:9999/query").with(
35
+ query: {u: "username", p: "password", q: "DROP DATABASE foo"}
36
+ )
37
+ end
38
+
39
+ it "should GET to remove a database" do
40
+ expect(subject.delete_database("foo")).to be_a(Net::HTTPOK)
41
+ end
42
+ end
43
+
44
+ describe "#list_databases" do
45
+ let(:response) { {"results"=>[{"series"=>[{"name"=>"databases", "columns"=>["name"], "values"=>[["foobar"]]}]}]} }
46
+ let(:expected_result) { [{"name"=>"foobar"}] }
47
+
48
+ before do
49
+ stub_request(:get, "http://influxdb.test:9999/query").with(
50
+ query: {u: "username", p: "password", q: "SHOW DATABASES"}
51
+ ).to_return(:body => JSON.generate(response), :status => 200)
52
+ end
53
+
54
+ it "should GET a list of databases" do
55
+ expect(subject.list_databases).to eq(expected_result)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe InfluxDB::Client do
5
+ let(:subject) do
6
+ described_class.new(
7
+ "database",
8
+ {
9
+ host: "influxdb.test",
10
+ port: 9999,
11
+ username: "username",
12
+ password: "password",
13
+ time_precision: "s"
14
+ }.merge(args)
15
+ )
16
+ end
17
+
18
+ let(:args) { {} }
19
+
20
+ ### TODO ###
21
+
22
+ # describe "DELETE #delete_series" do
23
+ # it "removes a series" do
24
+ # stub_request(:delete, "http://influxdb.test:9999/db/database/series/foo").with(
25
+ # query: { u: "username", p: "password" }
26
+ # )
27
+
28
+ # expect(subject.delete_series("foo")).to be_a(Net::HTTPOK)
29
+ # end
30
+ # end
31
+
32
+ # describe "GET #list_series" do
33
+ # it "returns a list of all series names" do
34
+ # data = [
35
+ # { "name" => "list_series_result",
36
+ # "columns" => %w(time name),
37
+ # "points" => [[0, 'a'], [0, 'b']]
38
+ # }
39
+ # ]
40
+
41
+ # stub_request(:get, "http://influxdb.test:9999/db/database/series").with(
42
+ # query: { u: "username", p: "password", q: "list series", time_precision: "s" }
43
+ # ).to_return(
44
+ # body: JSON.generate(data)
45
+ # )
46
+
47
+ # expect(subject.list_series).to eq %w(a b)
48
+ # end
49
+ # end
50
+ end
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe InfluxDB::Client do
5
+ let(:subject) do
6
+ described_class.new(
7
+ "database",
8
+ {
9
+ host: "influxdb.test",
10
+ port: 9999,
11
+ username: "username",
12
+ password: "password",
13
+ time_precision: "s"
14
+ }.merge(args)
15
+ )
16
+ end
17
+
18
+ let(:args) { {} }
19
+
20
+ ### TODO ###
21
+
22
+ # describe "GET #list_shards" do
23
+ # it "returns a list of shards" do
24
+ # shard_list = { "longTerm" => [], "shortTerm" => [] }
25
+ # stub_request(:get, "http://influxdb.test:9999/cluster/shards").with(
26
+ # query: { u: "username", p: "password" }
27
+ # ).to_return(body: JSON.generate(shard_list, status: 200))
28
+
29
+ # expect(subject.list_shards).to eq shard_list
30
+ # end
31
+ # end
32
+
33
+ # describe "DELETE #delete_shard" do
34
+ # it "removes shard by id" do
35
+ # shard_id = 1
36
+ # stub_request(:delete, "http://influxdb.test:9999/cluster/shards/#{shard_id}").with(
37
+ # query: { u: "username", p: "password" }
38
+ # )
39
+
40
+ # expect(subject.delete_shard(shard_id, [1, 2])).to be_a(Net::HTTPOK)
41
+ # end
42
+ # end
43
+ end
@@ -0,0 +1,127 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe InfluxDB::Client do
5
+ let(:subject) do
6
+ described_class.new(
7
+ "database",
8
+ {
9
+ host: "influxdb.test",
10
+ port: 9999,
11
+ username: "username",
12
+ password: "password",
13
+ time_precision: "s"
14
+ }.merge(args)
15
+ )
16
+ end
17
+
18
+ let(:args) { {} }
19
+
20
+ describe "#update user password" do
21
+ let(:user) { 'useruser' }
22
+ let(:pass) { 'passpass' }
23
+ let(:query) { "SET PASSWORD FOR #{user} = '#{pass}'" }
24
+
25
+ before do
26
+ stub_request(:get, "http://influxdb.test:9999/query").with(
27
+ query: {u: "username", p: "password", q: query}
28
+ )
29
+ end
30
+
31
+ it "should GET to update user password" do
32
+ expect(subject.update_user_password(user, pass)).to be_a(Net::HTTPOK)
33
+ end
34
+ end
35
+
36
+ describe "#grant_user_privileges" do
37
+ let(:user) { 'useruser' }
38
+ let(:perm) { :write }
39
+ let(:db) { 'foo' }
40
+ let(:query) { "GRANT #{perm.to_s.upcase} ON #{db} TO #{user}" }
41
+
42
+ before do
43
+ stub_request(:get, "http://influxdb.test:9999/query").with(
44
+ query: {u: "username", p: "password", q: query}
45
+ )
46
+ end
47
+
48
+ it "should GET to grant privileges for a user on a database" do
49
+ expect(subject.grant_user_privileges(user, db, perm)).to be_a(Net::HTTPOK)
50
+ end
51
+ end
52
+
53
+ describe "#revoke_user_privileges" do
54
+ let(:user) { 'useruser' }
55
+ let(:perm) { :write }
56
+ let(:db) { 'foo' }
57
+ let(:query) { "REVOKE #{perm.to_s.upcase} ON #{db} FROM #{user}" }
58
+
59
+ before do
60
+ stub_request(:get, "http://influxdb.test:9999/query").with(
61
+ query: {u: "username", p: "password", q: query}
62
+ )
63
+ end
64
+
65
+ it "should GET to revoke privileges from a user on a database" do
66
+ expect(subject.revoke_user_privileges(user, db, perm)).to be_a(Net::HTTPOK)
67
+ end
68
+ end
69
+
70
+ describe "#create_database_user" do
71
+ let(:user) { 'useruser' }
72
+ let(:pass) { 'passpass' }
73
+ let(:db) { 'foo' }
74
+ let(:query) { "CREATE user #{user} WITH PASSWORD '#{pass}'; GRANT ALL ON #{db} TO #{user}" }
75
+
76
+ before do
77
+ stub_request(:get, "http://influxdb.test:9999/query").with(
78
+ query: {u: "username", p: "password", q: query}
79
+ )
80
+ end
81
+
82
+ context "without specifying permissions" do
83
+ it "should GET to create a new database user with all permissions" do
84
+ expect(subject.create_database_user(db, user, pass)).to be_a(Net::HTTPOK)
85
+ end
86
+ end
87
+
88
+ context "with passing permission as argument" do
89
+ let(:permission) { :read }
90
+ let(:query) { "CREATE user #{user} WITH PASSWORD '#{pass}'; GRANT #{permission.to_s.upcase} ON #{db} TO #{user}" }
91
+
92
+ it "should GET to create a new database user with permission set" do
93
+ expect(subject.create_database_user(db, user, pass, permissions: permission)).to be_a(Net::HTTPOK)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "#delete_user" do
99
+ let(:user) { 'useruser' }
100
+ let(:query) { "DROP USER #{user}" }
101
+
102
+ before do
103
+ stub_request(:get, "http://influxdb.test:9999/query").with(
104
+ query: {u: "username", p: "password", q: query}
105
+ )
106
+ end
107
+
108
+ it "should GET to delete a user" do
109
+ expect(subject.delete_user(user)).to be_a(Net::HTTPOK)
110
+ end
111
+ end
112
+
113
+ describe "#list_users" do
114
+ let(:response) { {"results"=>[{"series"=>[{"columns"=>["user", "admin"], "values"=>[["dbadmin", true], ["foobar", false]]}]}]} }
115
+ let(:expected_result) { [{"username"=>"dbadmin", "admin"=>true}, {"username"=>"foobar", "admin"=>false}] }
116
+
117
+ before do
118
+ stub_request(:get, "http://influxdb.test:9999/query").with(
119
+ query: {u: "username", p: "password", q: "SHOW USERS"}
120
+ ).to_return(:body => JSON.generate(response, :status => 200))
121
+ end
122
+
123
+ it "should GET a list of database users" do
124
+ expect(subject.list_users).to eq(expected_result)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,159 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe InfluxDB::Client do
5
+ let(:subject) do
6
+ described_class.new(
7
+ "database",
8
+ {
9
+ host: "influxdb.test",
10
+ port: 9999,
11
+ username: "username",
12
+ password: "password",
13
+ time_precision: "s"
14
+ }.merge(args)
15
+ )
16
+ end
17
+
18
+ let(:args) { {} }
19
+ let(:database) { subject.config.database }
20
+
21
+ describe "#query" do
22
+
23
+ context "with single series with multiple points" do
24
+ let(:response) do
25
+ {"results"=>[{"series"=>[{"name"=>"cpu", "tags"=>{"region"=>"us"},
26
+ "columns"=>["time", "temp", "value"],
27
+ "values"=>[["2015-07-07T14:58:37Z", 92, 0.3445], ["2015-07-07T14:59:09Z", 68, 0.8787]]}]}]}
28
+ end
29
+ let(:expected_result) do
30
+ [{"name"=>"cpu", "tags"=>{"region"=>"us"},
31
+ "values"=>[{"time"=>"2015-07-07T14:58:37Z", "temp"=>92, "value"=>0.3445},
32
+ {"time"=>"2015-07-07T14:59:09Z", "temp"=>68, "value"=>0.8787}]}]
33
+ end
34
+ let(:query) { 'SELECT * FROM cpu' }
35
+
36
+ before do
37
+ stub_request(:get, "http://influxdb.test:9999/query").with(
38
+ :query => {:q => query, :u => "username", :p => "password", :precision => 's', :db => database},
39
+ ).to_return(:body => JSON.generate(response))
40
+ end
41
+
42
+ it "should return array with single hash containing multiple values" do
43
+ expect(subject.query(query)).to eq(expected_result)
44
+ end
45
+ end
46
+
47
+ context "with series with different tags" do
48
+ let(:response) do
49
+ {"results"=>
50
+ [{"series"=>
51
+ [{"name"=>"cpu", "tags"=>{"region"=>"pl"}, "columns"=>["time", "temp", "value"], "values"=>[["2015-07-07T15:13:04Z", 34, 0.343443]]},
52
+ {"name"=>"cpu", "tags"=>{"region"=>"us"}, "columns"=>["time", "temp", "value"], "values"=>[["2015-07-07T14:58:37Z", 92, 0.3445], ["2015-07-07T14:59:09Z", 68, 0.8787]]}]}]}
53
+ end
54
+ let(:expected_result) do
55
+ [{"name"=>"cpu", "tags"=>{"region"=>"pl"},
56
+ "values"=>[{"time"=>"2015-07-07T15:13:04Z", "temp"=>34, "value"=>0.343443}]},
57
+ {"name"=>"cpu", "tags"=>{"region"=>"us"},
58
+ "values"=>[{"time"=>"2015-07-07T14:58:37Z", "temp"=>92, "value"=>0.3445},
59
+ {"time"=>"2015-07-07T14:59:09Z", "temp"=>68, "value"=>0.8787}]}]
60
+ end
61
+ let(:query) { 'SELECT * FROM cpu' }
62
+
63
+ before do
64
+ stub_request(:get, "http://influxdb.test:9999/query").with(
65
+ :query => {:q => query, :u => "username", :p => "password", :precision => 's', :db => database},
66
+ ).to_return(:body => JSON.generate(response))
67
+ end
68
+
69
+ it "should return array with 2 elements grouped by tags" do
70
+ expect(subject.query(query)).to eq(expected_result)
71
+ end
72
+ end
73
+
74
+ context "with multiple series with different tags" do
75
+ let(:response) do
76
+ {"results"=>
77
+ [{"series"=>
78
+ [{"name"=>"access_times.service_1", "tags"=>{"code"=>"200", "result"=>"failure", "status"=>"OK"}, "columns"=>["time", "value"], "values"=>[["2015-07-08T07:15:22Z", 327]]},
79
+ {"name"=>"access_times.service_1", "tags"=>{"code"=>"500", "result"=>"failure", "status"=>"Internal Server Error"}, "columns"=>["time", "value"], "values"=>[["2015-07-08T06:15:22Z", 873]]},
80
+ {"name"=>"access_times.service_2", "tags"=>{"code"=>"200", "result"=>"failure", "status"=>"OK"}, "columns"=>["time", "value"], "values"=>[["2015-07-08T07:15:22Z", 943]]},
81
+ {"name"=>"access_times.service_2", "tags"=>{"code"=>"500", "result"=>"failure", "status"=>"Internal Server Error"}, "columns"=>["time", "value"], "values"=>[["2015-07-08T06:15:22Z", 606]]}]}]}
82
+ end
83
+ let(:expected_result) do
84
+ [{"name"=>"access_times.service_1", "tags"=>{"code"=>"200", "result"=>"failure", "status"=>"OK"}, "values"=>[{"time"=>"2015-07-08T07:15:22Z", "value"=>327}]},
85
+ {"name"=>"access_times.service_1", "tags"=>{"code"=>"500", "result"=>"failure", "status"=>"Internal Server Error"}, "values"=>[{"time"=>"2015-07-08T06:15:22Z", "value"=>873}]},
86
+ {"name"=>"access_times.service_2", "tags"=>{"code"=>"200", "result"=>"failure", "status"=>"OK"}, "values"=>[{"time"=>"2015-07-08T07:15:22Z", "value"=>943}]},
87
+ {"name"=>"access_times.service_2", "tags"=>{"code"=>"500", "result"=>"failure", "status"=>"Internal Server Error"}, "values"=>[{"time"=>"2015-07-08T06:15:22Z", "value"=>606}]}]
88
+ end
89
+ let(:query) { "SELECT * FROM /access_times.*/" }
90
+
91
+ before do
92
+ stub_request(:get, "http://influxdb.test:9999/query").with(
93
+ :query => {:q => query, :u => "username", :p => "password", :precision => 's', :db => database},
94
+ ).to_return(:body => JSON.generate(response))
95
+ end
96
+
97
+ it "should return array with 4 elements grouped by name and tags" do
98
+ expect(subject.query(query)).to eq(expected_result)
99
+ end
100
+ end
101
+
102
+ context "with multiple series for explicit value only" do
103
+ let(:response) do
104
+ {"results"=>
105
+ [{"series"=>
106
+ [{"name"=>"access_times.service_1", "columns"=>["time", "value"], "values"=>[["2015-07-08T06:15:22Z", 873], ["2015-07-08T07:15:22Z", 327]]},
107
+ {"name"=>"access_times.service_2", "columns"=>["time", "value"], "values"=>[["2015-07-08T06:15:22Z", 606], ["2015-07-08T07:15:22Z", 943]]}]}]}
108
+ end
109
+ let(:expected_result) do
110
+ [{"name"=>"access_times.service_1", "tags"=>nil, "values"=>[{"time"=>"2015-07-08T06:15:22Z", "value"=>873}, {"time"=>"2015-07-08T07:15:22Z", "value"=>327}]},
111
+ {"name"=>"access_times.service_2", "tags"=>nil, "values"=>[{"time"=>"2015-07-08T06:15:22Z", "value"=>606}, {"time"=>"2015-07-08T07:15:22Z", "value"=>943}]}]
112
+ end
113
+ let(:query) { "SELECT value FROM /access_times.*/" }
114
+
115
+ before do
116
+ stub_request(:get, "http://influxdb.test:9999/query").with(
117
+ :query => {:q => query, :u => "username", :p => "password", :precision => 's', :db => database},
118
+ ).to_return(:body => JSON.generate(response))
119
+ end
120
+
121
+ it "should return array with 2 elements grouped by name only and no tags" do
122
+ expect(subject.query(query)).to eq(expected_result)
123
+ end
124
+ end
125
+
126
+ context "with a block" do
127
+ let(:response) do
128
+ {"results"=>
129
+ [{"series"=>
130
+ [{"name"=>"cpu", "tags"=>{"region"=>"pl"}, "columns"=>["time", "temp", "value"], "values"=>[["2015-07-07T15:13:04Z", 34, 0.343443]]},
131
+ {"name"=>"cpu", "tags"=>{"region"=>"us"}, "columns"=>["time", "temp", "value"], "values"=>[["2015-07-07T14:58:37Z", 92, 0.3445], ["2015-07-07T14:59:09Z", 68, 0.8787]]}]}]}
132
+ end
133
+
134
+ let(:expected_result) do
135
+ [{"name"=>"cpu", "tags"=>{"region"=>"pl"},
136
+ "values"=>[{"time"=>"2015-07-07T15:13:04Z", "temp"=>34, "value"=>0.343443}]},
137
+ {"name"=>"cpu", "tags"=>{"region"=>"us"},
138
+ "values"=>[{"time"=>"2015-07-07T14:58:37Z", "temp"=>92, "value"=>0.3445},
139
+ {"time"=>"2015-07-07T14:59:09Z", "temp"=>68, "value"=>0.8787}]}]
140
+ end
141
+ let(:query) { 'SELECT * FROM cpu' }
142
+
143
+ before do
144
+ stub_request(:get, "http://influxdb.test:9999/query").with(
145
+ :query => {:q => query, :u => "username", :p => "password", :precision => 's', :db => database},
146
+ ).to_return(:body => JSON.generate(response))
147
+ end
148
+
149
+ it "should accept a block and yield name, tags and points" do
150
+ results = []
151
+ subject.query(query) do |name, tags, points|
152
+ results << {'name' => name, 'tags' => tags, 'values' => points}
153
+ end
154
+ expect(results).to eq(expected_result)
155
+ end
156
+ end
157
+ end
158
+
159
+ end