flare-tools 0.1.4 → 0.4.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Flare-tools.txt +0 -0
- data/History.txt +114 -2
- data/LICENSE +21 -0
- data/Manifest.txt +65 -8
- data/README.txt +356 -0
- data/Rakefile +90 -25
- data/Tutorial.txt +370 -0
- data/bin/flare-admin +6 -0
- data/bin/flare-argv0 +6 -0
- data/bin/flare-deploy +6 -0
- data/bin/flare-keychecker +6 -0
- data/bin/flare-part +6 -0
- data/bin/flare-ping +6 -0
- data/bin/flare-stats +4 -10
- data/bin/flare-zkadmin +6 -0
- data/lib/flare/net/connection.rb +98 -0
- data/lib/flare/test/cluster.rb +140 -0
- data/lib/flare/test/daemon.rb +144 -0
- data/lib/flare/test/node.rb +62 -0
- data/lib/flare/tools.rb +18 -16
- data/lib/flare/tools/cli.rb +32 -0
- data/lib/flare/tools/cli/activate.rb +106 -0
- data/lib/flare/tools/cli/balance.rb +83 -0
- data/lib/flare/tools/cli/cli_util.rb +77 -0
- data/lib/flare/tools/cli/deploy.rb +170 -0
- data/lib/flare/tools/cli/down.rb +85 -0
- data/lib/flare/tools/cli/dump.rb +219 -0
- data/lib/flare/tools/cli/dumpkey.rb +117 -0
- data/lib/flare/tools/cli/flare_admin.rb +81 -0
- data/lib/flare/tools/cli/flare_argv0.rb +60 -0
- data/lib/flare/tools/cli/flare_keychecker.rb +106 -0
- data/lib/flare/tools/cli/flare_zkadmin.rb +226 -0
- data/lib/flare/tools/cli/index.rb +54 -0
- data/lib/flare/tools/cli/list.rb +93 -0
- data/lib/flare/tools/cli/master.rb +143 -0
- data/lib/flare/tools/cli/part.rb +100 -0
- data/lib/flare/tools/cli/ping.rb +81 -0
- data/lib/flare/tools/cli/reconstruct.rb +164 -0
- data/lib/flare/tools/cli/remove.rb +119 -0
- data/lib/flare/tools/cli/restore.rb +180 -0
- data/lib/flare/tools/cli/slave.rb +125 -0
- data/lib/flare/tools/cli/stats.rb +229 -122
- data/lib/flare/tools/cli/sub_command.rb +73 -0
- data/lib/flare/tools/cli/summary.rb +97 -0
- data/lib/flare/tools/cli/threads.rb +78 -0
- data/lib/flare/tools/cli/verify.rb +202 -0
- data/lib/flare/tools/client.rb +267 -0
- data/lib/flare/tools/cluster.rb +319 -0
- data/lib/flare/tools/common.rb +196 -0
- data/lib/flare/tools/index_server.rb +51 -0
- data/lib/flare/tools/node.rb +162 -0
- data/lib/flare/tools/stats.rb +75 -0
- data/lib/flare/tools/zk_util.rb +28 -0
- data/lib/flare/util.rb +34 -0
- data/lib/flare/util/bwlimit.rb +132 -0
- data/lib/flare/util/command_line.rb +79 -0
- data/lib/flare/util/conf.rb +71 -0
- data/lib/flare/util/constant.rb +25 -0
- data/lib/flare/util/conversion.rb +26 -0
- data/lib/flare/util/default_logger.rb +52 -0
- data/lib/flare/util/exception.rb +19 -0
- data/lib/flare/util/filesystem.rb +30 -0
- data/lib/flare/util/flared_conf.rb +33 -0
- data/lib/flare/util/flarei_conf.rb +32 -0
- data/lib/flare/util/hash_function.rb +32 -0
- data/lib/flare/util/interruption.rb +70 -0
- data/lib/flare/util/key_resolver.rb +67 -0
- data/lib/flare/util/log4r_logger.rb +79 -0
- data/lib/flare/util/logger.rb +40 -0
- data/lib/flare/util/logging.rb +84 -0
- data/lib/flare/util/result.rb +53 -0
- data/test/test/experimental/cache_test.rb +113 -0
- data/test/test/experimental/key_distribution_test.rb +38 -0
- data/test/test/experimental/keychecker_test.rb +60 -0
- data/test/test/experimental/list_test.rb +108 -0
- data/test/test/extra/replication_test.rb +184 -0
- data/test/test/integration/cli_test.rb +348 -0
- data/test/test/integration/dump_expired_test.rb +103 -0
- data/test/test/integration/dump_test.rb +128 -0
- data/test/test/integration/index_server_test.rb +35 -0
- data/test/test/integration/node_test.rb +78 -0
- data/test/test/integration/partition_test.rb +235 -0
- data/test/test/integration/proxy_test.rb +54 -0
- data/test/test/integration/stats_test.rb +79 -0
- data/test/test/system/flare_admin_test.rb +191 -0
- data/test/test/unit/bwlimit_test.rb +52 -0
- data/test/test/unit/cluster_test.rb +96 -0
- data/test/test/unit/daemon_test.rb +30 -0
- data/test/test/unit/logger_test.rb +46 -0
- data/test/test/unit/tools_test.rb +25 -0
- data/test/test/unit/util_test.rb +70 -0
- metadata +239 -84
- data/README.rdoc +0 -83
- data/bin/flare-partition-setting +0 -12
- data/lib/flare/tools/cli/partition_setting.rb +0 -86
- data/lib/flare/tools/core.rb +0 -189
- data/lib/flare/tools/logger.rb +0 -31
- data/test/test_flare-tools.rb +0 -11
- data/test/test_helper.rb +0 -3
@@ -0,0 +1,73 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'flare/util/logging'
|
7
|
+
require 'flare/util/interruption'
|
8
|
+
|
9
|
+
module Flare
|
10
|
+
module Tools
|
11
|
+
module Cli
|
12
|
+
class SubCommand
|
13
|
+
@@myname = {}
|
14
|
+
@@desc = {}
|
15
|
+
@@usage = {}
|
16
|
+
|
17
|
+
S_OK = 0
|
18
|
+
S_NG = 1
|
19
|
+
|
20
|
+
def self.to_sym
|
21
|
+
myname
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.myname(myname = nil)
|
25
|
+
if myname.nil? then @@myname[name] else @@myname[name] = myname end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.usage(usage = nil)
|
29
|
+
if usage.nil?
|
30
|
+
@@usage[name] = "" unless @@usage.has_key?(name)
|
31
|
+
@@usage[name]
|
32
|
+
else
|
33
|
+
@@usage[name] = usage
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.desc(desc = nil)
|
38
|
+
if desc.nil?
|
39
|
+
@@desc[name] = "" unless @@desc.has_key?(name)
|
40
|
+
@@desc[name]
|
41
|
+
else
|
42
|
+
@@desc[name] = desc
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def myname
|
47
|
+
myname = @@myname[self.class.name]
|
48
|
+
if myname.nil? then "" else myname end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.to_s
|
52
|
+
self.to_sym.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def initalize
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup(opt)
|
59
|
+
raise "setup"
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute(config, *args)
|
63
|
+
raise "execute"
|
64
|
+
end
|
65
|
+
|
66
|
+
include Flare::Util::Logging
|
67
|
+
include Flare::Util::Interruption
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'thread'
|
7
|
+
require 'flare/tools/index_server'
|
8
|
+
require 'flare/tools/cli/sub_command'
|
9
|
+
require 'flare/tools/common'
|
10
|
+
require 'flare/util/conversion'
|
11
|
+
|
12
|
+
#
|
13
|
+
module Flare
|
14
|
+
module Tools
|
15
|
+
module Cli
|
16
|
+
|
17
|
+
# == Description
|
18
|
+
#
|
19
|
+
class Summary < SubCommand
|
20
|
+
include Flare::Util::Conversion
|
21
|
+
include Flare::Util::Logging
|
22
|
+
include Flare::Tools::Common
|
23
|
+
|
24
|
+
myname :summary
|
25
|
+
desc "show the summary of a flare cluster."
|
26
|
+
usage "summary [hostname:port] ..."
|
27
|
+
|
28
|
+
HeaderConfig = [ ['%-30.30s', '#cluster'],
|
29
|
+
['%6s', 'part'],
|
30
|
+
['%6s', 'node'],
|
31
|
+
['%7s', 'master'],
|
32
|
+
['%6s', 'slave'],
|
33
|
+
['%16s', 'size'],
|
34
|
+
['%13s', 'items'] ]
|
35
|
+
|
36
|
+
def setup(opt)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute(config, *args)
|
44
|
+
nodes = {}
|
45
|
+
threads = {}
|
46
|
+
header = HeaderConfig
|
47
|
+
|
48
|
+
Flare::Tools::IndexServer.open(config[:index_server_hostname], config[:index_server_port], config[:timeout]) do |s|
|
49
|
+
nodes = s.stats_nodes
|
50
|
+
unless nodes
|
51
|
+
error "Invalid index server."
|
52
|
+
return S_NG
|
53
|
+
end
|
54
|
+
nodes = nodes.sort_by{|key,val| [val['partition'].to_i, val['role'], key]}
|
55
|
+
end
|
56
|
+
|
57
|
+
name = if config[:cluster].nil? then
|
58
|
+
"#{config[:index_server_hostname]}:#{config[:index_server_port]}"
|
59
|
+
else
|
60
|
+
config[:cluster]
|
61
|
+
end
|
62
|
+
|
63
|
+
total_bytes = 0
|
64
|
+
total_parts = 0
|
65
|
+
total_nodes = 0
|
66
|
+
total_items = 0
|
67
|
+
total_masters = 0
|
68
|
+
total_slaves = 0
|
69
|
+
|
70
|
+
nodes.each do |hostname_port,data|
|
71
|
+
hostname, port = hostname_port.split(":", 2)
|
72
|
+
Flare::Tools::Stats.open(hostname, data['port'], config[:timeout]) do |s|
|
73
|
+
stats = s.stats
|
74
|
+
p = data['partition'].to_i
|
75
|
+
total_parts = p+1 if p+1 > total_parts if data['role'] == 'master'
|
76
|
+
total_nodes += 1
|
77
|
+
total_masters += 1 if data['role'] == 'master'
|
78
|
+
total_slaves += 1 if data['role'] == 'slave'
|
79
|
+
total_bytes += stats['bytes'].to_i if data['role'] == 'master'
|
80
|
+
total_items += stats['curr_items'].to_i if data['role'] == 'master'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
format = header.map {|x| x[0]}.join(@delimiter)
|
85
|
+
label = format % header.map{|x| x[1]}.flatten
|
86
|
+
puts label
|
87
|
+
puts(format % [name, total_parts, total_nodes, total_masters, total_slaves, total_bytes, total_items])
|
88
|
+
|
89
|
+
S_OK
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'flare/tools/index_server'
|
7
|
+
require 'flare/util/conversion'
|
8
|
+
require 'flare/tools/common'
|
9
|
+
require 'flare/tools/cli/sub_command'
|
10
|
+
|
11
|
+
module Flare
|
12
|
+
module Tools
|
13
|
+
module Cli
|
14
|
+
class Threads < SubCommand
|
15
|
+
include Flare::Util::Conversion
|
16
|
+
include Flare::Tools::Common
|
17
|
+
|
18
|
+
myname :threads
|
19
|
+
desc "show the list of threads in a flare cluster."
|
20
|
+
usage "threads [hostname:port]"
|
21
|
+
|
22
|
+
def setup(opt)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute(config, *args)
|
29
|
+
header = [
|
30
|
+
['%5s', 'id'],
|
31
|
+
['%-32s', 'peer'],
|
32
|
+
['%-13s', 'operation'],
|
33
|
+
['%4s', 'type'],
|
34
|
+
['%8s', 'queue']
|
35
|
+
]
|
36
|
+
format = header.map {|x| x[0]}.join(' ')
|
37
|
+
|
38
|
+
hostname = config[:index_server_hostname]
|
39
|
+
port = config[:index_server_port]
|
40
|
+
|
41
|
+
if args.size == 1
|
42
|
+
nodekey = nodekey_of args[0]
|
43
|
+
if nodekey.nil?
|
44
|
+
error "invalid nodekey: "+args[0]
|
45
|
+
return S_NG
|
46
|
+
end
|
47
|
+
hostname, port = nodekey.split(':')
|
48
|
+
elsif args.size > 1
|
49
|
+
error "invalid arguments: "+args.join(' ')
|
50
|
+
return S_NG
|
51
|
+
end
|
52
|
+
|
53
|
+
threads = []
|
54
|
+
|
55
|
+
Flare::Tools::Stats.open(hostname, port, config[:timeout]) do |s|
|
56
|
+
threads = s.stats_threads
|
57
|
+
threads = threads.sort_by{|key,val| [val['peer'], key]}
|
58
|
+
end
|
59
|
+
|
60
|
+
puts format % header.map{|x| x[1]}.flatten
|
61
|
+
|
62
|
+
threads.each do |thread_id, data|
|
63
|
+
puts format % [
|
64
|
+
thread_id,
|
65
|
+
data['peer'],
|
66
|
+
if data['op'].nil? then "-" else data['op'] end,
|
67
|
+
data['type'],
|
68
|
+
data['queue'],
|
69
|
+
]
|
70
|
+
end
|
71
|
+
|
72
|
+
S_OK
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'flare/tools/index_server'
|
7
|
+
require 'flare/tools/node'
|
8
|
+
require 'flare/util/conversion'
|
9
|
+
require 'flare/util/key_resolver'
|
10
|
+
require 'flare/util/hash_function'
|
11
|
+
require 'flare/tools/cli/sub_command'
|
12
|
+
require 'digest/md5'
|
13
|
+
|
14
|
+
|
15
|
+
#
|
16
|
+
module Flare
|
17
|
+
module Tools
|
18
|
+
module Cli
|
19
|
+
|
20
|
+
# == Description
|
21
|
+
#
|
22
|
+
class Verify < SubCommand
|
23
|
+
include Flare::Util::Conversion
|
24
|
+
include Flare::Util::HashFunction
|
25
|
+
|
26
|
+
myname :verify
|
27
|
+
desc "verify the cluster. (experimental)"
|
28
|
+
usage "verify"
|
29
|
+
|
30
|
+
def setup(opt)
|
31
|
+
opt.on('--key-hash-algorithm=TYPE', "key hash algorithm") do |v|
|
32
|
+
case @key_hash_algorithm = v.to_sym
|
33
|
+
when :simple, :crc32
|
34
|
+
else
|
35
|
+
puts "unknown type: #{v}"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
end
|
39
|
+
opt.on('--use-test-data', "store test data") {|v| @use_test_data = true}
|
40
|
+
opt.on('--debug', "use debug mode") {|v| @debug = true}
|
41
|
+
opt.on('--64bit', "(experimental) 64bit mode") {|v| @word_size = 64}
|
42
|
+
opt.on('--verbose', "use verbose mode") {|v| @verbose = true}
|
43
|
+
opt.on('--meta', "use meta command") {|v| @meta = true}
|
44
|
+
opt.on('--quiet', "use quiet mode") {|v| @quiet = true}
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
super
|
49
|
+
@numeric_hosts = false
|
50
|
+
@key_hash_algorithm = :simple
|
51
|
+
@use_test_data = false
|
52
|
+
@debug = false
|
53
|
+
@word_size = 32
|
54
|
+
@bwlimit = 0
|
55
|
+
@verbose = false
|
56
|
+
@meta = false
|
57
|
+
@quiet = false
|
58
|
+
end
|
59
|
+
|
60
|
+
def execute(config, *args)
|
61
|
+
keys = {}
|
62
|
+
cout = STDERR
|
63
|
+
status = S_OK
|
64
|
+
info "connecting to index ..."
|
65
|
+
Flare::Tools::IndexServer.open(config[:index_server_hostname], config[:index_server_port], config[:timeout]) do |s|
|
66
|
+
nodes = s.stats_nodes.sort_by{|key, val| [val['partition'].to_i, val['role'], key]}
|
67
|
+
|
68
|
+
# meta
|
69
|
+
info "setting up key resolver ..."
|
70
|
+
resolver = nil
|
71
|
+
if @meta
|
72
|
+
meta = s.meta
|
73
|
+
stats = s.stats
|
74
|
+
kha = meta['key-hash-algorithm']
|
75
|
+
if kha
|
76
|
+
@key_hash_algorithm = :crc32 if kha == 'crc32'
|
77
|
+
@key_hash_algorithm = :simple if kha == 'simple'
|
78
|
+
else
|
79
|
+
@key_hash_algorithm = :simple
|
80
|
+
end
|
81
|
+
pointer_size = stats['pointer_size']
|
82
|
+
option = {
|
83
|
+
:partition_size => meta['partition-size'].to_i,
|
84
|
+
:virtual => meta['partition-modular-virtual'].to_i,
|
85
|
+
:hint => meta['partition-modular-hint'].to_i
|
86
|
+
}
|
87
|
+
resolver = Util::KeyResolver.new(:modular, option)
|
88
|
+
else
|
89
|
+
resolver = Util::KeyResolver.new
|
90
|
+
end
|
91
|
+
info "key_hash_algorithm = #{@key_hash_algorithm.to_s}"
|
92
|
+
|
93
|
+
# check node list size
|
94
|
+
if nodes.size == 0
|
95
|
+
cout.puts "no nodes"
|
96
|
+
return S_NG
|
97
|
+
end
|
98
|
+
hostname0, port0 = nodes[0][0].split(":", 2)
|
99
|
+
|
100
|
+
# partition size
|
101
|
+
partition_size = 1+nodes.inject(-1) do |r,entry|
|
102
|
+
node, val = entry
|
103
|
+
i = val['partition'].to_i
|
104
|
+
if i >= r then i else r end
|
105
|
+
end
|
106
|
+
if partition_size <= 0
|
107
|
+
info "no need to verify."
|
108
|
+
return S_NG
|
109
|
+
end
|
110
|
+
info "partition_size: #{partition_size}"
|
111
|
+
|
112
|
+
if @use_test_data
|
113
|
+
info "storing test data ..."
|
114
|
+
Flare::Tools::Node.open(hostname0, port0.to_i, config[:timeout]) do |n|
|
115
|
+
(1..10000).each do |i|
|
116
|
+
key = ".test."+Digest::MD5.new.update(i.to_s).to_s
|
117
|
+
n.set(key, i.to_s)
|
118
|
+
keys[key] = :not_found
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
nodes.each do |nodekey,val|
|
124
|
+
hostname, port = nodekey.split(":", 2)
|
125
|
+
partition = val['partition'].to_i
|
126
|
+
Flare::Tools::Node.open(hostname, port.to_i, config[:timeout]) do |n|
|
127
|
+
cout.write "checking #{nodekey} ... "
|
128
|
+
msg = "OK"
|
129
|
+
interruptible do
|
130
|
+
count = 0
|
131
|
+
cout.write "keydump ... "
|
132
|
+
n.dumpkey(partition, partition_size) do |key|
|
133
|
+
next if key.nil?
|
134
|
+
type = @key_hash_algorithm
|
135
|
+
hash = get_key_hash_value(key, type, @word_size)
|
136
|
+
p = resolver.resolve(hash, partition_size)
|
137
|
+
count += 1
|
138
|
+
if p != partition then
|
139
|
+
cout.puts "keydump failed: the partition for #{key}(#{hash}) is #{p} but it was dumpped from #{partition}." if @debug
|
140
|
+
status = S_NG
|
141
|
+
msg = "NG"
|
142
|
+
else
|
143
|
+
keys[key] = :found
|
144
|
+
end
|
145
|
+
false
|
146
|
+
end
|
147
|
+
cout.write "#{count} entries. "
|
148
|
+
count = 0
|
149
|
+
cout.write "dump ... "
|
150
|
+
n.dump(0, partition, partition_size, @bwlimit) do |data, key, flag, len, version, expire|
|
151
|
+
next if key.nil?
|
152
|
+
type = @key_hash_algorithm
|
153
|
+
hash = get_key_hash_value(key, type, @word_size)
|
154
|
+
p = resolver.resolve(hash, partition_size)
|
155
|
+
count += 1
|
156
|
+
if p != partition then
|
157
|
+
cout.puts "dump failed: the partition for #{key}(#{hash}) is #{p} but it was dumpped from #{partition}." if @debug
|
158
|
+
status = S_NG
|
159
|
+
msg = "NG"
|
160
|
+
end
|
161
|
+
false
|
162
|
+
end
|
163
|
+
cout.write "#{count} entries. "
|
164
|
+
end # interruptible
|
165
|
+
cout.write "#{msg}\n"
|
166
|
+
end # Node.open
|
167
|
+
end # nodes.each
|
168
|
+
|
169
|
+
if @use_test_data && keys.size > 0
|
170
|
+
# check total result
|
171
|
+
remain = 0
|
172
|
+
keys.each do |k,state|
|
173
|
+
if state != :found
|
174
|
+
error "failed: not found '#{k}'" if @verbose
|
175
|
+
remain += 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
error "failed: not found #{remain} keys" if remain > 0
|
179
|
+
|
180
|
+
# delete
|
181
|
+
Flare::Tools::Node.open(hostname0, port0.to_i, config[:timeout]) do |n|
|
182
|
+
keys.each do |k,v|
|
183
|
+
n.delete(k)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# end of connection
|
189
|
+
end
|
190
|
+
if status == S_OK
|
191
|
+
cout.puts "OK"
|
192
|
+
else
|
193
|
+
cout.puts "NG"
|
194
|
+
end
|
195
|
+
status
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|