mogilefs-client 2.2.0 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.document +11 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +4 -0
  4. data/.wrongdoc.yml +5 -0
  5. data/GIT-VERSION-GEN +28 -0
  6. data/GNUmakefile +44 -0
  7. data/HACKING +33 -0
  8. data/{History.txt → History} +0 -1
  9. data/{LICENSE.txt → LICENSE} +0 -1
  10. data/Manifest.txt +34 -7
  11. data/README +51 -0
  12. data/Rakefile +11 -11
  13. data/TODO +10 -0
  14. data/bin/mog +109 -68
  15. data/examples/mogstored_rack.rb +189 -0
  16. data/lib/mogilefs.rb +56 -17
  17. data/lib/mogilefs/admin.rb +128 -62
  18. data/lib/mogilefs/backend.rb +205 -95
  19. data/lib/mogilefs/bigfile.rb +54 -70
  20. data/lib/mogilefs/bigfile/filter.rb +58 -0
  21. data/lib/mogilefs/chunker.rb +30 -0
  22. data/lib/mogilefs/client.rb +0 -2
  23. data/lib/mogilefs/copy_stream.rb +30 -0
  24. data/lib/mogilefs/http_file.rb +175 -0
  25. data/lib/mogilefs/http_reader.rb +79 -0
  26. data/lib/mogilefs/mogilefs.rb +242 -148
  27. data/lib/mogilefs/mysql.rb +3 -4
  28. data/lib/mogilefs/paths_size.rb +24 -0
  29. data/lib/mogilefs/pool.rb +0 -1
  30. data/lib/mogilefs/socket.rb +9 -0
  31. data/lib/mogilefs/socket/kgio.rb +55 -0
  32. data/lib/mogilefs/socket/pure_ruby.rb +70 -0
  33. data/lib/mogilefs/socket_common.rb +58 -0
  34. data/lib/mogilefs/util.rb +6 -169
  35. data/test/aggregate.rb +11 -11
  36. data/test/exec.rb +72 -0
  37. data/test/fresh.rb +222 -0
  38. data/test/integration.rb +43 -0
  39. data/test/setup.rb +1 -0
  40. data/test/socket_test.rb +98 -0
  41. data/test/test_admin.rb +14 -37
  42. data/test/test_backend.rb +50 -107
  43. data/test/test_bigfile.rb +2 -2
  44. data/test/test_db_backend.rb +1 -2
  45. data/test/test_fresh.rb +8 -0
  46. data/test/test_http_reader.rb +34 -0
  47. data/test/test_mogilefs.rb +278 -98
  48. data/test/test_mogilefs_integration.rb +174 -0
  49. data/test/test_mogilefs_integration_large_pipe.rb +62 -0
  50. data/test/test_mogilefs_integration_list_keys.rb +40 -0
  51. data/test/test_mogilefs_socket_kgio.rb +11 -0
  52. data/test/test_mogilefs_socket_pure.rb +7 -0
  53. data/test/test_mogstored_rack.rb +89 -0
  54. data/test/test_mogtool_bigfile.rb +116 -0
  55. data/test/test_mysql.rb +1 -2
  56. data/test/test_pool.rb +1 -1
  57. data/test/test_unit_mogstored_rack.rb +72 -0
  58. metadata +76 -54
  59. data/README.txt +0 -80
  60. data/lib/mogilefs/httpfile.rb +0 -157
  61. data/lib/mogilefs/network.rb +0 -107
  62. data/test/test_network.rb +0 -56
  63. data/test/test_util.rb +0 -121
data/test/exec.rb ADDED
@@ -0,0 +1,72 @@
1
+ # -*- encoding: binary -*-
2
+ $stdout.sync = $stderr.sync = true
3
+ Thread.abort_on_exception = true
4
+ require 'test/unit'
5
+ require 'securerandom'
6
+ require 'tempfile'
7
+ require 'digest'
8
+ require 'stringio'
9
+ require 'pp'
10
+ require 'mogilefs'
11
+
12
+ module TestExec
13
+ def uuid
14
+ SecureRandom.uuid
15
+ rescue NoMethodError
16
+ ary = SecureRandom.random_bytes(16).unpack("NnnnnN")
17
+ ary[2] = (ary[2] & 0x0fff) | 0x4000
18
+ ary[3] = (ary[3] & 0x3fff) | 0x8000
19
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
20
+ end
21
+
22
+ def yield_for_monitor_update # mogilefsd should update every 4 seconds
23
+ 50.times do
24
+ yield
25
+ sleep 0.1
26
+ end
27
+ end
28
+
29
+ def mogadm(*args)
30
+ x("mogadm", "--trackers=#{@trackers.join(',')}", *args)
31
+ end
32
+
33
+ def x(*cmd)
34
+ out, err = tmpfile("out"), tmpfile("err")
35
+ puts cmd.join(' ') if $VERBOSE
36
+ pid = fork do
37
+ $stderr.reopen(err.path, "a")
38
+ $stdout.reopen(out.path, "a")
39
+ out.close
40
+ err.close
41
+ ObjectSpace.each_object(Tempfile) do |tmp|
42
+ next if tmp.closed?
43
+ ObjectSpace.undefine_finalizer(tmp)
44
+ tmp.close_on_exec = true if tmp.respond_to?(:close_on_exec=)
45
+ end
46
+ exec(*cmd)
47
+ end
48
+ _, status = Process.waitpid2(pid)
49
+ out.rewind
50
+ err.rewind
51
+ [ status, out, err ]
52
+ end
53
+
54
+ def mogadm!(*args)
55
+ status, out, err = mogadm(*args)
56
+ assert status.success?, "#{status.inspect} / #{out.read} / #{err.read}"
57
+ [ status, out, err ]
58
+ end
59
+
60
+ def x!(*cmd)
61
+ status, out, err = x(*cmd)
62
+ assert status.success?, "#{status.inspect} / #{out.read} / #{err.read}"
63
+ [ status, out, err ]
64
+ end
65
+
66
+ def tmpfile(name)
67
+ tmp = Tempfile.new(name)
68
+ defined?(@to_close) or @to_close = []
69
+ @to_close << tmp
70
+ tmp
71
+ end
72
+ end
data/test/fresh.rb ADDED
@@ -0,0 +1,222 @@
1
+ # -*- encoding: binary -*-
2
+ require "./test/exec"
3
+ require "tmpdir"
4
+ require "fileutils"
5
+ require "net/http"
6
+
7
+ module TestFreshSetup
8
+ include TestExec
9
+
10
+ def setup
11
+ setup_mogilefs
12
+ end
13
+
14
+ def setup_mogilefs(plugins = nil)
15
+ @test_host = "127.0.0.1"
16
+ setup_mogstored
17
+ @tracker = TCPServer.new(@test_host, 0)
18
+ @tracker_port = @tracker.addr[1]
19
+
20
+ @dbname = Tempfile.new(["mogfresh", ".sqlite3"])
21
+ @mogilefsd_conf = Tempfile.new(["mogilefsd", "conf"])
22
+ @mogilefsd_pid = Tempfile.new(["mogilefsd", "pid"])
23
+
24
+ cmd = %w(mogdbsetup --yes --type=SQLite --dbname) << @dbname.path
25
+ x!(*cmd)
26
+
27
+ @mogilefsd_conf.puts "db_dsn DBI:SQLite:#{@dbname.path}"
28
+ @mogilefsd_conf.write <<EOF
29
+ conf_port #@tracker_port
30
+ listen #@test_host
31
+ pidfile #{@mogilefsd_pid.path}
32
+ replicate_jobs 1
33
+ fsck_jobs 1
34
+ query_jobs 1
35
+ mogstored_stream_port #{@mogstored_mgmt_port}
36
+ node_timeout 10
37
+ EOF
38
+ @mogilefsd_conf.flush
39
+
40
+ @trackers = @hosts = [ "#@test_host:#@tracker_port" ]
41
+ @tracker.close
42
+ x!("mogilefsd", "--daemon", "--config=#{@mogilefsd_conf.path}")
43
+ wait_for_port @tracker_port
44
+ @admin = MogileFS::Admin.new(:hosts => @hosts)
45
+ 50.times do
46
+ break if File.size(@mogstored_pid.path) > 0
47
+ sleep 0.1
48
+ end
49
+ end
50
+
51
+ def wait_for_port(port)
52
+ tries = 50
53
+ begin
54
+ TCPSocket.new(@test_host, port).close
55
+ return
56
+ rescue
57
+ sleep 0.1
58
+ end while (tries -= 1) > 0
59
+ raise "#@test_host:#{port} never became ready"
60
+ end
61
+
62
+ def test_admin_setup_new_host_and_devices
63
+ assert_equal [], @admin.get_hosts
64
+ args = { :ip => @test_host, :port => @mogstored_http_port }
65
+ @admin.create_host("me", args)
66
+ yield_for_monitor_update { @admin.get_hosts.empty? or break }
67
+ hosts = @admin.get_hosts
68
+ assert_equal 1, hosts.size
69
+ host = @admin.get_hosts[0]
70
+ assert_equal "me", host["hostname"]
71
+ assert_equal @mogstored_http_port, host["http_port"]
72
+ assert_nil host["http_get_port"]
73
+ assert_equal @test_host, host["hostip"]
74
+ assert_kind_of Integer, host["hostid"]
75
+ assert_equal hosts, @admin.get_hosts(host["hostid"])
76
+
77
+ assert_equal [], @admin.get_devices
78
+ end
79
+
80
+ def test_replicate_now
81
+ assert_equal({"count" => 0}, @admin.replicate_now)
82
+ end
83
+
84
+ def test_clear_cache
85
+ assert_nil @admin.clear_cache
86
+ end
87
+
88
+ def test_create_update_delete_class
89
+ domain = "rbmogtest#{Time.now.strftime('%Y%m%d%H%M%S')}.#{uuid}"
90
+ @admin.create_domain(domain)
91
+ yield_for_monitor_update { @admin.get_domains.include?(domain) and break }
92
+
93
+ assert_nothing_raised do
94
+ @admin.create_class(domain, "klassy", 1)
95
+ end
96
+ assert_raises(MogileFS::Backend::ClassExistsError) do
97
+ @admin.create_class(domain, "klassy", 1)
98
+ end
99
+
100
+ assert_nothing_raised do
101
+ @admin.update_class(domain, "klassy",
102
+ :mindevcount => 1, :replpolicy => "MultipleHosts(1)")
103
+ end
104
+
105
+ tmp = nil
106
+ yield_for_monitor_update do
107
+ tmp = @admin.get_domains[domain]["klassy"]
108
+ break if tmp && tmp["replpolicy"] == "MultipleHosts(1)"
109
+ end
110
+ assert tmp, "domain did not show up"
111
+ assert_equal 1, tmp["mindevcount"]
112
+ assert_equal "MultipleHosts(1)", tmp["replpolicy"]
113
+ assert_nothing_raised { @admin.update_class(domain, "klassy", 2) }
114
+ ensure
115
+ @admin.delete_class(domain, "klassy") rescue nil
116
+ end
117
+
118
+ def add_host_device_domain
119
+ assert_equal [], @admin.get_hosts
120
+ args = { :ip => @test_host, :port => @mogstored_http_port }
121
+ args[:status] = "alive"
122
+ @admin.create_host("me", args)
123
+ Dir.mkdir("#@docroot/dev1")
124
+ Dir.mkdir("#@docroot/dev2")
125
+ yield_for_monitor_update { @admin.get_hosts.empty? or break }
126
+
127
+ # TODO: allow adding devices via our MogileFS::Admin class
128
+ mogadm!("device", "add", "me", "dev1")
129
+ yield_for_monitor_update { @admin.get_devices.empty? or break }
130
+ wait_for_usage_file "dev1"
131
+ mogadm!("device", "add", "me", "dev2")
132
+ wait_for_usage_file "dev2"
133
+ out = err = nil
134
+ tries = 0
135
+ begin
136
+ out.close! if out
137
+ err.close! if err
138
+ status, out, err = mogadm("check")
139
+ if (tries += 1) > 100
140
+ warn err.read
141
+ puts out.read
142
+ raise "mogadm failed"
143
+ end
144
+ sleep 0.1
145
+ end until out.read =~ /write?able/
146
+
147
+ domain = "rbmogtest.#$$"
148
+ @admin.create_domain(domain)
149
+ yield_for_monitor_update { @admin.get_domains.include?(domain) and break }
150
+ @domain = domain
151
+ end
152
+
153
+ def test_device_file_add
154
+ add_host_device_domain
155
+ client = MogileFS::MogileFS.new :hosts => @hosts, :domain => @domain
156
+ r, w = IO.pipe
157
+ thr = Thread.new do
158
+ (0..9).each do |i|
159
+ sleep 0.05
160
+ w.write("#{i}\n")
161
+ end
162
+ w.close
163
+ :ok
164
+ end
165
+ assert_equal 20, client.store_file("pipe", nil, r)
166
+ assert_equal :ok, thr.value
167
+ r.close
168
+ assert_equal "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n", client.get_file_data("pipe")
169
+ end
170
+
171
+ def teardown_mogilefs
172
+ if @mogstored_pid
173
+ pid = File.read(@mogstored_pid.path).to_i
174
+ Process.kill(:TERM, pid) if pid > 0
175
+ end
176
+ if @mogilefsd_pid
177
+ s = TCPSocket.new(@test_host, @tracker_port)
178
+ s.write "!shutdown\r\n"
179
+ s.close
180
+ end
181
+ FileUtils.rmtree(@docroot)
182
+ end
183
+
184
+ def wait_for_usage_file(device)
185
+ uri = URI("http://#@test_host:#@mogstored_http_port/#{device}/usage")
186
+ res = nil
187
+ 100.times do
188
+ res = Net::HTTP.get_response(uri)
189
+ if Net::HTTPOK === res
190
+ puts res.body if $DEBUG
191
+ return
192
+ end
193
+ puts res.inspect if $DEBUG
194
+ sleep 0.1
195
+ end
196
+ raise "#{uri} failed to appear: #{res.inspect}"
197
+ end
198
+
199
+ def setup_mogstored
200
+ @docroot = Dir.mktmpdir(["mogfresh", "docroot"])
201
+ @mogstored_mgmt = TCPServer.new(@test_host, 0)
202
+ @mogstored_http = TCPServer.new(@test_host, 0)
203
+ @mogstored_mgmt_port = @mogstored_mgmt.addr[1]
204
+ @mogstored_http_port = @mogstored_http.addr[1]
205
+ @mogstored_conf = Tempfile.new(["mogstored", "conf"])
206
+ @mogstored_pid = Tempfile.new(["mogstored", "pid"])
207
+ @mogstored_conf.write <<EOF
208
+ pidfile = #{@mogstored_pid.path}
209
+ maxconns = 1000
210
+ httplisten = #@test_host:#{@mogstored_http_port}
211
+ mgmtlisten = #@test_host:#{@mogstored_mgmt_port}
212
+ docroot = #@docroot
213
+ EOF
214
+ @mogstored_conf.flush
215
+ @mogstored_mgmt.close
216
+ @mogstored_http.close
217
+
218
+ x!("mogstored", "--daemon", "--config=#{@mogstored_conf.path}")
219
+ wait_for_port @mogstored_mgmt_port
220
+ wait_for_port @mogstored_http_port
221
+ end
222
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: binary -*-
2
+ require './test/exec'
3
+
4
+ class TestMogIntegration < Test::Unit::TestCase
5
+ include TestExec
6
+
7
+ def test_dummy
8
+ assert true, "Ruby 1.8 Test::Unit is broken"
9
+ end unless defined?(MiniTest)
10
+
11
+ def setup
12
+ @to_close = []
13
+ @trackers = ENV["MOG_TEST_TRACKERS"].split(/,/)
14
+ domain = "rbmogtest#{Time.now.strftime('%Y%m%d%H%M%S')}.#{uuid}"
15
+ @admin = MogileFS::Admin.new(:hosts => @trackers)
16
+ @admin.create_domain(domain)
17
+ yield_for_monitor_update do
18
+ @admin.get_domains.include?(domain) and break
19
+ end
20
+ @domain = domain
21
+ end
22
+
23
+ def teardown
24
+ if defined?(@domain)
25
+ client = MogileFS::MogileFS.new :hosts => @trackers, :domain => @domain
26
+ client.each_key("") { |key|
27
+ p [ :delete, key ] if $VERBOSE
28
+ client.delete(key)
29
+ }
30
+ assert_equal true, @admin.delete_domain(@domain)
31
+ assert_raises(MogileFS::Backend::DomainNotFoundError) do
32
+ @admin.delete_domain(@domain)
33
+ end
34
+ end
35
+ @to_close.each do |io|
36
+ io.closed? or io.close
37
+ end
38
+ end
39
+ end if ENV["MOG_TEST_TRACKERS"]
40
+
41
+ class TestMogIntegration
42
+ warn "MOG_TEST_TRACKERS not defined"
43
+ end unless ENV["MOG_TEST_TRACKERS"]
data/test/setup.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  STDIN.sync = STDOUT.sync = STDERR.sync = true
3
+ Thread.abort_on_exception = true
3
4
  require 'test/unit'
4
5
  require 'tempfile'
5
6
  require 'fileutils'
@@ -0,0 +1,98 @@
1
+ require "socket"
2
+ require "test/unit"
3
+ module SocketTest
4
+
5
+ def setup
6
+ @host = ENV["TEST_HOST"] || '127.0.0.1'
7
+ @srv = TCPServer.new(@host, 0)
8
+ @port = @srv.addr[1]
9
+ end
10
+
11
+ def test_start
12
+ sock = MogileFS::Socket.start(@host, @port)
13
+ assert_instance_of MogileFS::Socket, sock, sock.inspect
14
+ assert_nothing_raised do
15
+ begin
16
+ sock.write_nonblock("a")
17
+ rescue Errno::EAGAIN
18
+ end
19
+ end
20
+ thr = Thread.new { @srv.accept }
21
+ accepted = thr.value
22
+ assert_instance_of TCPSocket, accepted, accepted.inspect
23
+ assert_nil sock.close
24
+ end
25
+
26
+ def test_new
27
+ sock = MogileFS::Socket.tcp(@host, @port)
28
+ assert_instance_of MogileFS::Socket, sock, sock.inspect
29
+ assert_nothing_raised do
30
+ sock.write_nonblock("a")
31
+ end
32
+ thr = Thread.new { @srv.accept }
33
+ accepted = thr.value
34
+ assert_instance_of TCPSocket, accepted, accepted.inspect
35
+ assert_equal "a", accepted.read(1)
36
+ assert_nil sock.close
37
+ end
38
+
39
+ def test_timed_peek
40
+ sock = MogileFS::Socket.tcp(@host, @port)
41
+ accepted = @srv.accept
42
+ buf = ""
43
+ assert_raises(MogileFS::UnreadableSocketError) do
44
+ sock.timed_peek(2, buf, 0.01)
45
+ end
46
+ accepted.write "HI"
47
+ assert_equal "HI", sock.timed_peek(2, buf, 0.1)
48
+ assert_equal "HI", buf
49
+ assert_equal "HI", sock.timed_peek(2, buf)
50
+ assert_equal "HI", sock.timed_read(2, buf)
51
+ accepted.close
52
+ assert_nil sock.timed_peek(2, buf)
53
+ end
54
+
55
+ def test_timed_read
56
+ sock = MogileFS::Socket.tcp(@host, @port)
57
+ accepted = @srv.accept
58
+ buf = ""
59
+ assert_raises(MogileFS::UnreadableSocketError) do
60
+ sock.timed_read(2, buf, 0.01)
61
+ end
62
+ accepted.write "HI"
63
+ assert_equal "HI", sock.timed_read(2, buf, 0.1)
64
+ assert_equal "HI", buf
65
+ assert_raises(MogileFS::UnreadableSocketError) do
66
+ sock.timed_read(2, buf, 0.01)
67
+ end
68
+ accepted.close
69
+ assert_nil sock.timed_read(2, buf)
70
+ end
71
+
72
+ def test_timed_write
73
+ sock = MogileFS::Socket.tcp(@host, @port)
74
+ accepted = @srv.accept
75
+ buf = "A" * 100000
76
+ written = 0
77
+ assert_raises(MogileFS::RequestTruncatedError) do
78
+ loop do
79
+ assert_equal buf.bytesize, sock.timed_write(buf, 0.01)
80
+ written += buf.size
81
+ end
82
+ end
83
+ tmp = accepted.read(written)
84
+ assert_equal written, tmp.size
85
+ end
86
+
87
+ def timed_gets
88
+ sock = MogileFS::Socket.tcp(@host, @port)
89
+ accepted = @srv.accept
90
+ assert_raises(MogileFS::UnreadableSocketError) { sock.timed_gets(0.01) }
91
+ accepted.write "HI"
92
+ assert_raises(MogileFS::UnreadableSocketError) { sock.timed_gets(0.01) }
93
+ accepted.write "\n"
94
+ assert_equal "HI\n", sock.timed_gets
95
+ accepted.close
96
+ assert_nil sock.timed_gets
97
+ end
98
+ end
data/test/test_admin.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
- require 'test/setup'
2
+ require './test/setup'
3
3
 
4
4
  class TestMogileFS__Admin < TestMogileFS
5
5
 
@@ -35,11 +35,6 @@ class TestMogileFS__Admin < TestMogileFS
35
35
  end
36
36
 
37
37
  def test_each_fid
38
- @backend.stats = {
39
- 'fidmax' => '182',
40
- 'fidcount' => '2',
41
- }
42
-
43
38
  @backend.list_fids = {
44
39
  'fid_count' => '1',
45
40
  'fid_1_fid' => '99',
@@ -59,22 +54,23 @@ class TestMogileFS__Admin < TestMogileFS
59
54
  'fid_1_key' => 'new_new_key',
60
55
  'fid_1_length' => '9',
61
56
  }
57
+ @backend.list_fids = { 'fid_count' => 0 }
62
58
 
63
59
  fids = []
64
60
  @client.each_fid { |fid| fids << fid }
65
61
 
66
62
  expected = [
67
- { "fid" => "99",
63
+ { "fid" => 99,
68
64
  "class" => "normal",
69
65
  "domain" => "test",
70
- "devcount" => "2",
71
- "length" => "4",
66
+ "devcount" => 2,
67
+ "length" => 4,
72
68
  "key" => "file_key" },
73
- { "fid" => "182",
69
+ { "fid" => 182,
74
70
  "class" => "normal",
75
- "devcount" => "2",
71
+ "devcount" => 2,
76
72
  "domain" => "test",
77
- "length" => "9",
73
+ "length" => 9,
78
74
  "key" => "new_new_key" },
79
75
  ]
80
76
 
@@ -104,25 +100,6 @@ class TestMogileFS__Admin < TestMogileFS
104
100
  assert_equal expected, @client.get_domains
105
101
  end
106
102
 
107
- def disabled_test_get_stats
108
- @backend.stats = {}
109
-
110
- expected = {
111
- 'fids' => { 'max' => '99', 'count' => '2' },
112
- 'device' => [
113
- { 'status' => 'alive', 'files' => '2', 'id' => '1', 'host' => 'rur-1' },
114
- { 'status' => 'alive', 'files' => '2', 'id' => '2', 'host' => 'rur-2' }
115
- ],
116
- 'replication' => [
117
- { 'files' => '2', 'class' => 'normal', 'devcount' => '2',
118
- 'domain' => 'test' }
119
- ],
120
- 'file' => [{ 'files' => '2', 'class' => 'normal', 'domain' => 'test' }]
121
- }
122
-
123
- assert_equal
124
- end
125
-
126
103
  def test_get_stats_fids
127
104
  @backend.stats = {
128
105
  'fidmax' => 99,
@@ -154,17 +131,17 @@ class TestMogileFS__Admin < TestMogileFS
154
131
  }
155
132
 
156
133
  expected = [
157
- { "fid" => "99",
134
+ { "fid" => 99,
158
135
  "class" => "normal",
159
136
  "domain" => "test",
160
- "devcount" => "2",
161
- "length" => "4",
137
+ "devcount" => 2,
138
+ "length" => 4,
162
139
  "key" => "file_key" },
163
- { "fid" => "82",
140
+ { "fid" => 82,
164
141
  "class" => "normal",
165
- "devcount" => "2",
142
+ "devcount" => 2,
166
143
  "domain" => "test",
167
- "length" => "9",
144
+ "length" => 9,
168
145
  "key" => "new_new_key" },
169
146
  ]
170
147