mogilefs-client 2.2.0 → 3.0.0.rc1

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 (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