mu 5.7.2.3
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.
- data/Mu_Gem.html +1591 -0
- data/bin/mu +11 -0
- data/lib/mu.rb +65 -0
- data/lib/mu/api/ddt.rb +233 -0
- data/lib/mu/api/homepage.rb +54 -0
- data/lib/mu/api/muapi.rb +231 -0
- data/lib/mu/api/netconfig.rb +233 -0
- data/lib/mu/api/scale.rb +199 -0
- data/lib/mu/api/system.rb +40 -0
- data/lib/mu/client.rb +31 -0
- data/lib/mu/command.rb +28 -0
- data/lib/mu/command/api.rb +95 -0
- data/lib/mu/command/cmd_appid.rb +486 -0
- data/lib/mu/command/cmd_cli.rb +151 -0
- data/lib/mu/command/cmd_ddt.rb +449 -0
- data/lib/mu/command/cmd_homepage.rb +146 -0
- data/lib/mu/command/cmd_muapi.rb +361 -0
- data/lib/mu/command/cmd_netconfig.rb +262 -0
- data/lib/mu/command/cmd_runscale.rb +533 -0
- data/lib/mu/command/cmd_runscenario.rb +258 -0
- data/lib/mu/command/cmd_runverify.rb +336 -0
- data/lib/mu/command/cmd_scale.rb +333 -0
- data/lib/mu/command/cmd_system.rb +127 -0
- data/lib/mu/command/curl.rb +246 -0
- data/lib/mu/command/help.rb +29 -0
- data/lib/mu/curl/error.rb +54 -0
- data/lib/mu/curl/verify.rb +137 -0
- data/lib/mu/helper.rb +55 -0
- data/lib/mu/http_helper.rb +232 -0
- data/rdoc/classes/Mu.html +305 -0
- data/rdoc/classes/Mu/Client.html +265 -0
- data/rdoc/classes/Mu/Command.html +208 -0
- data/rdoc/classes/Mu/Command/API.html +524 -0
- data/rdoc/classes/Mu/Command/Cmd_appid.html +934 -0
- data/rdoc/classes/Mu/Command/Cmd_cli.html +515 -0
- data/rdoc/classes/Mu/Command/Cmd_ddt.html +1169 -0
- data/rdoc/classes/Mu/Command/Cmd_homepage.html +489 -0
- data/rdoc/classes/Mu/Command/Cmd_muapi.html +968 -0
- data/rdoc/classes/Mu/Command/Cmd_netconfig.html +743 -0
- data/rdoc/classes/Mu/Command/Cmd_runscale.html +970 -0
- data/rdoc/classes/Mu/Command/Cmd_runscenario.html +530 -0
- data/rdoc/classes/Mu/Command/Cmd_runverify.html +621 -0
- data/rdoc/classes/Mu/Command/Cmd_scale.html +939 -0
- data/rdoc/classes/Mu/Command/Cmd_system.html +426 -0
- data/rdoc/classes/Mu/Command/Curl.html +524 -0
- data/rdoc/classes/Mu/Command/Help.html +166 -0
- data/rdoc/classes/Mu/Curl.html +116 -0
- data/rdoc/classes/Mu/Curl/Error.html +157 -0
- data/rdoc/classes/Mu/Curl/Error/Authorize.html +178 -0
- data/rdoc/classes/Mu/Curl/Error/Connect.html +149 -0
- data/rdoc/classes/Mu/Curl/Error/DNS.html +113 -0
- data/rdoc/classes/Mu/Curl/Error/Region.html +160 -0
- data/rdoc/classes/Mu/Curl/Error/Status.html +149 -0
- data/rdoc/classes/Mu/Curl/Error/Timeout.html +149 -0
- data/rdoc/classes/Mu/Curl/Verify.html +282 -0
- data/rdoc/classes/Mu/Curl/Verify/Request.html +227 -0
- data/rdoc/classes/Mu/Curl/Verify/Response.html +187 -0
- data/rdoc/classes/Mu/Curl/Verify/Result.html +188 -0
- data/rdoc/classes/Mu/Ddt.html +914 -0
- data/rdoc/classes/Mu/Helper.html +308 -0
- data/rdoc/classes/Mu/Homepage.html +377 -0
- data/rdoc/classes/Mu/HttpHelper.html +639 -0
- data/rdoc/classes/Mu/Muapi.html +816 -0
- data/rdoc/classes/Mu/Netconfig.html +781 -0
- data/rdoc/classes/Mu/Scale.html +832 -0
- data/rdoc/classes/Mu/System.html +281 -0
- data/rdoc/classes/Object.html +148 -0
- data/rdoc/classes/TCTestMu.html +1793 -0
- data/rdoc/classes/Test.html +107 -0
- data/rdoc/classes/Test/Unit.html +107 -0
- data/rdoc/classes/Test/Unit/TestCase.html +113 -0
- data/rdoc/created.rid +1 -0
- data/rdoc/files/lib/mu/api/ddt_rb.html +101 -0
- data/rdoc/files/lib/mu/api/homepage_rb.html +101 -0
- data/rdoc/files/lib/mu/api/muapi_rb.html +101 -0
- data/rdoc/files/lib/mu/api/netconfig_rb.html +101 -0
- data/rdoc/files/lib/mu/api/scale_rb.html +101 -0
- data/rdoc/files/lib/mu/api/system_rb.html +101 -0
- data/rdoc/files/lib/mu/client_rb.html +101 -0
- data/rdoc/files/lib/mu/command/api_rb.html +101 -0
- data/rdoc/files/lib/mu/command/cmd_appid_rb.html +119 -0
- data/rdoc/files/lib/mu/command/cmd_cli_rb.html +108 -0
- data/rdoc/files/lib/mu/command/cmd_ddt_rb.html +117 -0
- data/rdoc/files/lib/mu/command/cmd_homepage_rb.html +115 -0
- data/rdoc/files/lib/mu/command/cmd_muapi_rb.html +116 -0
- data/rdoc/files/lib/mu/command/cmd_netconfig_rb.html +116 -0
- data/rdoc/files/lib/mu/command/cmd_runscale_rb.html +119 -0
- data/rdoc/files/lib/mu/command/cmd_runscenario_rb.html +115 -0
- data/rdoc/files/lib/mu/command/cmd_runverify_rb.html +117 -0
- data/rdoc/files/lib/mu/command/cmd_scale_rb.html +115 -0
- data/rdoc/files/lib/mu/command/cmd_system_rb.html +116 -0
- data/rdoc/files/lib/mu/command/curl_rb.html +101 -0
- data/rdoc/files/lib/mu/command/help_rb.html +101 -0
- data/rdoc/files/lib/mu/command_rb.html +107 -0
- data/rdoc/files/lib/mu/curl/error_rb.html +101 -0
- data/rdoc/files/lib/mu/curl/verify_rb.html +101 -0
- data/rdoc/files/lib/mu/helper_rb.html +101 -0
- data/rdoc/files/lib/mu/http_helper_rb.html +101 -0
- data/rdoc/files/lib/mu_rb.html +121 -0
- data/rdoc/files/test/helper_rb.html +112 -0
- data/rdoc/files/test/tc_test_mu_rb.html +111 -0
- data/rdoc/fr_class_index.html +68 -0
- data/rdoc/fr_file_index.html +55 -0
- data/rdoc/fr_method_index.html +374 -0
- data/rdoc/index.html +24 -0
- data/rdoc/rdoc-style.css +208 -0
- data/test/data/app_id_stats.csv +1 -0
- data/test/data/data_cgi.msl +94 -0
- data/test/data/data_cgi.xml +322 -0
- data/test/data/default_test.csv +3 -0
- data/test/data/ftp_with_channel.xml +1643 -0
- data/test/data/irc.xml +3837 -0
- data/test/data/scale_configuration.json +25 -0
- data/test/data/test_data_cgi_error.xml +35 -0
- data/test/helper.rb +18 -0
- data/test/tc_test_mu.rb +716 -0
- metadata +322 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Use these commands to access the legacy REST API for configuring Mu interfaces, network hosts, and routes
|
|
2
|
+
require 'mu/api/netconfig'
|
|
3
|
+
class Mu
|
|
4
|
+
class Command
|
|
5
|
+
class Cmd_netconfig < Command
|
|
6
|
+
|
|
7
|
+
attr_accessor :host, :username, :password, :api
|
|
8
|
+
|
|
9
|
+
# displays command-line help
|
|
10
|
+
def cmd_help argv
|
|
11
|
+
help
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# returns a json representation of the specified element
|
|
15
|
+
# * argv = command-line arguments, requires an element (-e) argument, such as 'interfaces', 'hosts' or 'routes' or 'interfaces/a1' or 'hosts/dell-9'
|
|
16
|
+
def cmd_get argv
|
|
17
|
+
setup argv
|
|
18
|
+
e = @hash['element']
|
|
19
|
+
response = @api.get(e)
|
|
20
|
+
msg JSON.pretty_generate(response)
|
|
21
|
+
return response
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# modifies a network element
|
|
25
|
+
# * argv = command-line arguments, requires a json configuration (-j) and the element (-e) to modify, such as 'interfaces', 'hosts' or 'routes' or 'interfaces/a1' or 'hosts/dell-9'
|
|
26
|
+
def cmd_modify argv
|
|
27
|
+
setup argv
|
|
28
|
+
json = @hash['json']
|
|
29
|
+
e = @hash['element']
|
|
30
|
+
response = @api.modify(json, e)
|
|
31
|
+
msg response
|
|
32
|
+
return response
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# creates a new network element
|
|
37
|
+
# * argv = command-line arguments, requires a json configuration (-j) and the element (-e) to modify, such as 'interfaces', 'hosts' or 'routes'
|
|
38
|
+
def cmd_create argv
|
|
39
|
+
setup argv
|
|
40
|
+
json = @hash['json']
|
|
41
|
+
e = @hash['element']
|
|
42
|
+
response = @api.create(json, e)
|
|
43
|
+
msg response
|
|
44
|
+
return response
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# deletes an existing network element
|
|
48
|
+
# * argv = command-line arguments, requires a json configuration (-j) and the element (-e) to modify, such as 'interfaces', 'hosts' or 'routes' or 'interfaces/a1' or 'hosts/dell-9'
|
|
49
|
+
def cmd_delete argv
|
|
50
|
+
setup argv
|
|
51
|
+
e = @hash['element']
|
|
52
|
+
response = @api.delete(e)
|
|
53
|
+
msg response
|
|
54
|
+
return response
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# restores the network configuration from a file
|
|
58
|
+
# * argv = command-line arguments, requires a path to a json configuration file (-f) , and a boolean (-b true|false) argument indicating whether or not existing elements should be cleared
|
|
59
|
+
def cmd_restore argv
|
|
60
|
+
setup argv
|
|
61
|
+
filepath = @hash['filepath']
|
|
62
|
+
clear_existing = to_boolean(@hash['boolean'])
|
|
63
|
+
response = @api.restore(filepath, clear_existing)
|
|
64
|
+
msg response
|
|
65
|
+
return response
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# clears existing network configuration hosts
|
|
69
|
+
# * argv = command-line arguments
|
|
70
|
+
def cmd_clear_hosts argv
|
|
71
|
+
setup argv
|
|
72
|
+
response = @api.clear_hosts
|
|
73
|
+
msg JSON.pretty_generate(response)
|
|
74
|
+
return response
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# resolves network configuration hosts
|
|
78
|
+
# * argv = command-line arguments, requires the name (-n) of the host to resolve (determines its ip address and adds it to the network configuration)
|
|
79
|
+
def cmd_resolve_hosts argv
|
|
80
|
+
setup argv
|
|
81
|
+
name = @hash['name']
|
|
82
|
+
response = @api.resolve_hosts(name)
|
|
83
|
+
msg response
|
|
84
|
+
return response
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# clears existing network interfaces
|
|
88
|
+
# * argv = command-line arguments, require the names of the interfaces to clear (-i name)
|
|
89
|
+
def cmd_clear_interface argv
|
|
90
|
+
setup argv
|
|
91
|
+
interface = @hash['interfaces']
|
|
92
|
+
response = @api.clear_interface(interface)
|
|
93
|
+
msg response
|
|
94
|
+
return response
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# clears existing vlan configurations
|
|
98
|
+
# * argv = command-line arguments
|
|
99
|
+
def cmd_clear_vlans argv
|
|
100
|
+
setup argv
|
|
101
|
+
response = @api.clear_vlans
|
|
102
|
+
msg response
|
|
103
|
+
return response
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# clears existing network routes
|
|
107
|
+
# * argv = command-line arguments
|
|
108
|
+
def cmd_clear_routes argv
|
|
109
|
+
setup argv
|
|
110
|
+
response = @api.clear_routes
|
|
111
|
+
msg response
|
|
112
|
+
return response
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# saves the network configuration to a file
|
|
117
|
+
# * argv = command-line arguments, requires the element (-e element, such as 'interfaces', or -e all) to save, and a file name (-f) to save the settings to
|
|
118
|
+
def cmd_save argv
|
|
119
|
+
setup argv
|
|
120
|
+
e = @hash['elements']
|
|
121
|
+
filepath = @hash['filepath'] || "config.json"
|
|
122
|
+
response = @api.save(e, filepath)
|
|
123
|
+
msg response
|
|
124
|
+
return response
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def setup argv
|
|
130
|
+
parse_cli argv
|
|
131
|
+
@host = (@@mu_ip.nil?) ? "127.0.0.1" : @@mu_ip
|
|
132
|
+
@username = (@@mu_admin_user.nil?) ? "admin" : @@mu_admin_user
|
|
133
|
+
@password = (@@mu_admin_pass.nil?) ? "admin" : @@mu_admin_pass
|
|
134
|
+
@response = nil
|
|
135
|
+
@element = "" # sticky variable will hold a default element, the last element specified
|
|
136
|
+
@api = Netconfig.new(@host, @username, @password)
|
|
137
|
+
msg "Created Netconfig API object to :#{@host}", Logger::DEBUG
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_cli argv
|
|
141
|
+
args = Array.new
|
|
142
|
+
@hash = {}
|
|
143
|
+
while not argv.empty?
|
|
144
|
+
args << argv.shift if argv.first[0,1] != '-'
|
|
145
|
+
|
|
146
|
+
k = argv.shift
|
|
147
|
+
|
|
148
|
+
if [ '-b', '--boolean' ].member? k
|
|
149
|
+
@hash['boolean'] = shift(k, argv)
|
|
150
|
+
next
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if [ '-e', '--element' ].member? k
|
|
154
|
+
@hash['element'] = shift(k, argv)
|
|
155
|
+
next
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if [ '-f', '--filepath' ].member? k
|
|
159
|
+
@hash['filepath'] = shift(k, argv)
|
|
160
|
+
next
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if [ '-h', '--help' ].member? k
|
|
164
|
+
help
|
|
165
|
+
exit
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
if [ '-i', '--interfaces' ].member? k
|
|
169
|
+
@hash['interfaces'] = shift(k, argv)
|
|
170
|
+
next
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if [ '-j', '--json' ].member? k
|
|
174
|
+
@hash['json'] = shift(k, argv)
|
|
175
|
+
next
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if [ '-m', '--mu_string' ].member? k
|
|
179
|
+
mu_string = shift(k, argv)
|
|
180
|
+
if mu_string =~ /(.+?):(.+?)@(.*)/
|
|
181
|
+
@@mu_admin_user = $1
|
|
182
|
+
@@mu_admin_pass = $2
|
|
183
|
+
@@mu_ip = $3
|
|
184
|
+
end
|
|
185
|
+
next
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if [ '-n', '--name' ].member? k
|
|
189
|
+
@hash['name'] = shift(k, argv)
|
|
190
|
+
next
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if [ '-o', '--output' ].member? k
|
|
194
|
+
$stdout.reopen(shift(k, argv), "w")
|
|
195
|
+
next
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
if [ '-r', '--routes' ].member? k
|
|
199
|
+
@hash['routes'] = shift(k, argv)
|
|
200
|
+
next
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
if [ '-v', '--verbose' ].member? k
|
|
204
|
+
$log.level = Logger::DEBUG
|
|
205
|
+
next
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
args
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def help
|
|
213
|
+
helps = [
|
|
214
|
+
{ :short => '-b', :long => '--boolean', :value => '<string>', :help => 'boolean arg' },
|
|
215
|
+
{ :short => '-e', :long => '--element', :value => '<string>', :help => 'http string' },
|
|
216
|
+
{ :short => '-f', :long => '--filepath', :value => '<string>', :help => 'filepath' },
|
|
217
|
+
{ :short => '-h', :long => '--help', :value => '', :help => 'help on command line options' },
|
|
218
|
+
{ :short => '-i', :long => '--interfaces', :value => '<string>', :help => 'interfaces/hosts' },
|
|
219
|
+
{ :short => '-j', :long => '--json', :value => '<string>', :help => 'json object' },
|
|
220
|
+
{ :short => '-m', :long => '--mu_string', :value => '<string>', :help => 'user, password, mu_ip in the form of admin:admin@10.9.8.7' },
|
|
221
|
+
{ :short => '-n', :long => '--name', :value => '<string>', :help => 'host name' },
|
|
222
|
+
{ :short => '-o', :long => '--output', :value => '<string>', :help => 'output logging to this file' },
|
|
223
|
+
{ :short => '-v', :long => '--verbose', :value => '', :help => 'set Logger::DEBUG level' }
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
cmds = [
|
|
227
|
+
"mu cmd_netconfig:clear_hosts",
|
|
228
|
+
"mu cmd_netconfig:clear_interfaces -i <interfaces>",
|
|
229
|
+
"mu cmd_netconfig:clear_routes",
|
|
230
|
+
"mu cmd_netconfig:clear_vlans",
|
|
231
|
+
"mu cmd_netconfig:create -j <json> -e <element>",
|
|
232
|
+
"mu cmd_netconfig:delete -e <element>",
|
|
233
|
+
"mu cmd_netconfig:get -e <element>",
|
|
234
|
+
"mu cmd_netconfig:help",
|
|
235
|
+
"mu cmd_netconfig:modify -j <json> -e <element>",
|
|
236
|
+
"mu cmd_netconfig:resolve_hosts -n <name>",
|
|
237
|
+
"mu cmd_netconfig:restore -f <filepath> [-b <clear_existing>]",
|
|
238
|
+
"mu cmd_netconfig:save -e <element> -f <filepath>",
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
max_long_size = helps.inject(0) { |memo, obj| [ obj[:long].size, memo ].max }
|
|
242
|
+
max_value_size = helps.inject(0) { |memo, obj| [ obj[:value].size, memo ].max }
|
|
243
|
+
puts
|
|
244
|
+
puts "Usage: mu cmd_netconfig <options>"
|
|
245
|
+
puts
|
|
246
|
+
helps.each do |h|
|
|
247
|
+
puts "%-*s %*s %-*s %s" % [max_long_size, h[:long], 2, h[:short], max_value_size, h[:value], h[:help]]
|
|
248
|
+
end
|
|
249
|
+
puts
|
|
250
|
+
puts "Available Commands"
|
|
251
|
+
puts
|
|
252
|
+
cmds.each do | c |
|
|
253
|
+
puts c
|
|
254
|
+
end
|
|
255
|
+
puts
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
end
|
|
259
|
+
end # Command
|
|
260
|
+
end # Mu
|
|
261
|
+
|
|
262
|
+
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
# Runs Mu Studio scenario msl files in Studio Scale, requiring that all interfaces and
|
|
2
|
+
# hosts used in the scenario are specified on the command-line through the –i option.
|
|
3
|
+
# Runs either a single msl file or a directory of msl files, and has command-line options
|
|
4
|
+
# to specify the Mu parameters, the interfaces to use, and the pattern in which to run
|
|
5
|
+
require 'mu/api/scale'
|
|
6
|
+
class Mu
|
|
7
|
+
class Command
|
|
8
|
+
class Cmd_runscale < Command
|
|
9
|
+
|
|
10
|
+
attr_accessor :api, :params, :hosts, :addr_indexes, :offset_indexes
|
|
11
|
+
|
|
12
|
+
# displays command-line help
|
|
13
|
+
# * argv = command-line arguments
|
|
14
|
+
def cmd_help argv
|
|
15
|
+
help
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# sets up, executes, and closes a Studio Scale test
|
|
19
|
+
# * argv = command-line arguments , requires a scenario (-s) argument
|
|
20
|
+
def cmd_run_file argv
|
|
21
|
+
setup argv
|
|
22
|
+
|
|
23
|
+
if not @hash['scenario']
|
|
24
|
+
msg "scenario required"
|
|
25
|
+
return help
|
|
26
|
+
else
|
|
27
|
+
if @hash['scenario'].include?(".msl")
|
|
28
|
+
scenario = @hash['scenario']
|
|
29
|
+
else # TODO: eventually, xml and mus file may be supported by scale api
|
|
30
|
+
msg "only .msl files are currently supported"
|
|
31
|
+
return help
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if not @hash['dir']
|
|
36
|
+
@dir = ""
|
|
37
|
+
path = scenario
|
|
38
|
+
else
|
|
39
|
+
@dir = @hash['dir']
|
|
40
|
+
path = @dir + "/" + scenario
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if !File.exists?(path)
|
|
44
|
+
raise "*** Error: File #{path} does not exist"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@api = Scale.new(@@mu_ip, @@mu_admin_user, @@mu_admin_pass)
|
|
48
|
+
@api.configure("pattern", @cmd_line_pattern)
|
|
49
|
+
@params = {}
|
|
50
|
+
@params["dir"] = @dir
|
|
51
|
+
@params["msl"] = scenario
|
|
52
|
+
@params["hosts"] = @cmd_line_hosts
|
|
53
|
+
run(scenario)
|
|
54
|
+
@api.release
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# sets up, executes, and closes a Studio Scale test for each scenario (.msl file) found in the specified directory
|
|
58
|
+
# * argv = command-line arguments, requires a directory (-d) argument
|
|
59
|
+
# * optional -r argument for recursive directory search (default is a flat directory)
|
|
60
|
+
def cmd_run_dir argv
|
|
61
|
+
setup argv
|
|
62
|
+
|
|
63
|
+
if not @hash['dir']
|
|
64
|
+
return help
|
|
65
|
+
else
|
|
66
|
+
@dir = @hash['dir']
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
|
70
|
+
File.delete("app_id_stats.csv") if File.exists?("app_id_stats.csv")
|
|
71
|
+
|
|
72
|
+
@api = Scale.new(@@mu_ip, @@mu_admin_user, @@mu_admin_pass)
|
|
73
|
+
@api.configure("pattern", @cmd_line_pattern)
|
|
74
|
+
@params = {}
|
|
75
|
+
@params["dir"] = @dir
|
|
76
|
+
@params["hosts"] = @cmd_line_hosts
|
|
77
|
+
Dir.chdir(@params["dir"])
|
|
78
|
+
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
|
79
|
+
if @hash['recursive'].nil?
|
|
80
|
+
files = Dir.glob("*.msl")
|
|
81
|
+
else
|
|
82
|
+
files = Dir.glob("**/*.msl")
|
|
83
|
+
end
|
|
84
|
+
if !files.empty?
|
|
85
|
+
files.sort.each do | f |
|
|
86
|
+
run(f)
|
|
87
|
+
output_csv(f)
|
|
88
|
+
sleep 2
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
msg "no msl files found in #{@dir}"
|
|
92
|
+
end
|
|
93
|
+
@api.release
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def setup argv
|
|
99
|
+
parse_cli argv
|
|
100
|
+
@params = {}
|
|
101
|
+
|
|
102
|
+
if @hash['test']
|
|
103
|
+
@verify_only = true
|
|
104
|
+
else
|
|
105
|
+
@verify_only = false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if not @hash['testset']
|
|
109
|
+
@testset = ""
|
|
110
|
+
else
|
|
111
|
+
@testset = @hash['testset']
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if not @hash['pattern']
|
|
115
|
+
@cmd_line_pattern = "{ \"iterations\": 1, \"intervals\": [ {\"iterations\":1, \"end\":100, \"start\":1, \"duration\":20 } ] }"
|
|
116
|
+
else
|
|
117
|
+
@cmd_line_pattern = @hash['pattern']
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if not @hash['interfaces']
|
|
121
|
+
@cmd_line_hosts = "b1,b2"
|
|
122
|
+
else
|
|
123
|
+
@cmd_line_hosts = @hash['interfaces']
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def run(scenario)
|
|
129
|
+
# assume the scenario and testset files are in dir unless they contain '/'
|
|
130
|
+
# in which case they are assumed to be absolute paths
|
|
131
|
+
if scenario.include?("/")
|
|
132
|
+
musl_file = scenario
|
|
133
|
+
else
|
|
134
|
+
musl_file = @params["dir"] + "/" + scenario
|
|
135
|
+
end
|
|
136
|
+
# msg musl_file, Logger::DEBUG
|
|
137
|
+
@api.configure("musl", File.read(musl_file))
|
|
138
|
+
|
|
139
|
+
unless @testset.empty?
|
|
140
|
+
if @testset.include?("/")
|
|
141
|
+
csv_file = @testset
|
|
142
|
+
else
|
|
143
|
+
csv_file = @params["dir"] + "/" + @testset
|
|
144
|
+
end
|
|
145
|
+
@api.configure("csv", File.read(csv_file))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
|
149
|
+
File.delete("app_id_stats.csv") if File.exists?("app_id_stats.csv")
|
|
150
|
+
|
|
151
|
+
configure_hosts
|
|
152
|
+
|
|
153
|
+
msg "verifying #{scenario} ..."
|
|
154
|
+
response = @api.verify
|
|
155
|
+
msg response, Logger::DEBUG
|
|
156
|
+
# sleep 3
|
|
157
|
+
v = parse_verify_response(response)
|
|
158
|
+
msg "#{v}", Logger::DEBUG
|
|
159
|
+
if v.nil?
|
|
160
|
+
msg "error in verify"
|
|
161
|
+
return
|
|
162
|
+
end
|
|
163
|
+
if @verify_only
|
|
164
|
+
msg v
|
|
165
|
+
return
|
|
166
|
+
end
|
|
167
|
+
msg "starting #{scenario} ..."
|
|
168
|
+
@api.start
|
|
169
|
+
start_time = Time.now.to_i
|
|
170
|
+
while true
|
|
171
|
+
sleep 5
|
|
172
|
+
status = @api.status
|
|
173
|
+
if !status.nil?
|
|
174
|
+
if !status["status"].nil?
|
|
175
|
+
if status["status"]["running"] == false
|
|
176
|
+
msg "running = #{status["status"]["running"]}", Logger::DEBUG
|
|
177
|
+
r = parse_status(status)
|
|
178
|
+
dump_status(status, musl_file)
|
|
179
|
+
return
|
|
180
|
+
else
|
|
181
|
+
r = parse_status(status)
|
|
182
|
+
end
|
|
183
|
+
else # status['status'].nil? ... no bonafide status was returned
|
|
184
|
+
time_now = Time.now.to_i
|
|
185
|
+
if time_now - start_time > 20
|
|
186
|
+
# puts "\nError: timing out after 20 seconds. Test had failed to start or verify"
|
|
187
|
+
break
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
ensure
|
|
193
|
+
msg "stopping #{scenario} ..."
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def cmd_running?
|
|
197
|
+
if @api.nil?
|
|
198
|
+
msg "false"
|
|
199
|
+
return
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
status = @api.status
|
|
203
|
+
if !status.nil?
|
|
204
|
+
if !status["status"].nil?
|
|
205
|
+
msg status["status"]["running"]
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
msg "false"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def configure_hosts
|
|
213
|
+
@hosts = Array.new
|
|
214
|
+
@addr_indexes = Array.new
|
|
215
|
+
@offset_indexes = Array.new
|
|
216
|
+
hosts = @params["hosts"]
|
|
217
|
+
if !hosts.nil?
|
|
218
|
+
p = hosts.split(",")
|
|
219
|
+
p.each do | h |
|
|
220
|
+
if h.include?("-") # b1-1000,b2-1 to indicate addr_count
|
|
221
|
+
q = h.split("-")
|
|
222
|
+
@hosts << q[0]
|
|
223
|
+
if q[1].include?(":") # -1000:20 to indicate offset within range
|
|
224
|
+
r = q[1].split(":")
|
|
225
|
+
@addr_indexes << r[0]
|
|
226
|
+
@offset_indexes << r[1]
|
|
227
|
+
else
|
|
228
|
+
@addr_indexes << q[1]
|
|
229
|
+
@offset_indexes << 1
|
|
230
|
+
end
|
|
231
|
+
else # default to the 1st addr index
|
|
232
|
+
@hosts << h
|
|
233
|
+
@addr_indexes << 1
|
|
234
|
+
@offset_indexes << 1
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
else
|
|
238
|
+
@hosts = ['b1','b2']
|
|
239
|
+
@addr_indexes = [1,1]
|
|
240
|
+
@offset_indexes = [1,1]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
set_hosts_byname(@hosts, @addr_indexes, @offset_indexes)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def set_hosts_byname(hosts=@hosts, count=[1,1], offset=[1,1], v4=true)
|
|
247
|
+
new_hosts = Array.new
|
|
248
|
+
str = ""
|
|
249
|
+
hosts.each_with_index do |n, i|
|
|
250
|
+
if n.match(/^[ab][1-4]$/) or n.include?(".") # possible vlan
|
|
251
|
+
if count[i] == 1 or count[i].nil?
|
|
252
|
+
str = "#{n}/*"
|
|
253
|
+
else
|
|
254
|
+
str = "#{n}/*,#{count[i]},#{offset[i]}"
|
|
255
|
+
end
|
|
256
|
+
msg "using host #{str}", Logger::DEBUG
|
|
257
|
+
else
|
|
258
|
+
@net = Netconfig.new
|
|
259
|
+
@net.setup(@hosts, @username, @password)
|
|
260
|
+
if v4
|
|
261
|
+
addr = @net.get("hosts/#{n}")['v4_addr']
|
|
262
|
+
else
|
|
263
|
+
addr = @net.get("hosts/#{n}")['v6_addr']
|
|
264
|
+
end
|
|
265
|
+
str = "#{addr}"
|
|
266
|
+
msg "using host #{str}", Logger::DEBUG
|
|
267
|
+
end
|
|
268
|
+
new_hosts << str
|
|
269
|
+
end
|
|
270
|
+
set_hosts(new_hosts)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# expects full strings: e.g. b1/12.89.0.1 ...
|
|
274
|
+
def set_hosts(hosts=["b1","b2"])
|
|
275
|
+
host_params = {}
|
|
276
|
+
|
|
277
|
+
# assign hosts to consecutive string keys, host_0, host_1, etc ...
|
|
278
|
+
hosts.each_with_index do | h, i |
|
|
279
|
+
host_params["host_#{i}"] = hosts[i]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# convert keys to symbols
|
|
283
|
+
# host_params.each_key { |k| host_params[k.to_sym] = host_params[k] }
|
|
284
|
+
new_host_params = {}
|
|
285
|
+
host_params.each_key { |k| new_host_params[k.to_sym] = host_params[k] }
|
|
286
|
+
|
|
287
|
+
=begin
|
|
288
|
+
# add default host, set to host_1 if it exists, unless specified on the command-line
|
|
289
|
+
if @hash['default_host'].nil?
|
|
290
|
+
new_host_params[:default_host] = new_host_params[:host_1] unless new_host_params[:host_1].nil?
|
|
291
|
+
else
|
|
292
|
+
new_host_params[:default_host] = @hash['default_host'] if !@hash['default_host'].nil?
|
|
293
|
+
end
|
|
294
|
+
=end
|
|
295
|
+
|
|
296
|
+
@api.configure("hosts", new_host_params)
|
|
297
|
+
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def dump_status(status, msl)
|
|
301
|
+
filename = "app_id_status.json"
|
|
302
|
+
f = File.open(filename, "a")
|
|
303
|
+
status["filename"] = msl
|
|
304
|
+
str = JSON.pretty_generate(status)
|
|
305
|
+
f.write(",") if !File.zero?(f) # if appending, we need to insert a comma
|
|
306
|
+
f.write(str)
|
|
307
|
+
f.close
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def output_csv(msl_file)
|
|
311
|
+
filename = "app_id_stats.csv"
|
|
312
|
+
f = File.open(filename, "a")
|
|
313
|
+
doc = "#{msl_file},#{@executed},#{@errors.to_i},#{@timeouts.to_i},#{@client_tx_bytes},#{@client_tx_msgs},#{@client_rx_bytes},#{@client_rx_msgs},#{@server_tx_bytes},#{@server_tx_msgs},#{@server_rx_bytes},#{@server_rx_msgs}\n"
|
|
314
|
+
File.open(filename, 'a') {|f| f.write(doc) }
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def parse_verify_response(response)
|
|
318
|
+
if response.nil? # || response.empty?
|
|
319
|
+
msg "*** error = no response received from /verify ***\n\n"
|
|
320
|
+
return nil
|
|
321
|
+
end
|
|
322
|
+
begin
|
|
323
|
+
msg JSON.pretty_generate(response), Logger::DEBUG
|
|
324
|
+
if !response["status"].nil?
|
|
325
|
+
if response["status"]["error"] == true
|
|
326
|
+
@error = response["status"]["error"]
|
|
327
|
+
@reason = response["status"]["reason"]
|
|
328
|
+
dump_status(response)
|
|
329
|
+
msg "*** Error = #{@error}, reason = #{@reason} ***\n\n"
|
|
330
|
+
return nil
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
msg "*** verify: okay ***", Logger::DEBUG
|
|
334
|
+
return "*** verify: okay ***"
|
|
335
|
+
rescue => e
|
|
336
|
+
msg e, Logger::DEBUG
|
|
337
|
+
return nil
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def parse_status(status)
|
|
342
|
+
return nil if status.nil?
|
|
343
|
+
@reported_volume = 0
|
|
344
|
+
if !status["status"]["error"].nil?
|
|
345
|
+
if status["status"]["error"] == true
|
|
346
|
+
@error = status["status"]["error"]
|
|
347
|
+
@reason = status["status"]["reason"]
|
|
348
|
+
msg "*** Error = #{@error}, reason = #{@reason} ***\n\n"
|
|
349
|
+
return nil
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
@stats_summary = status["status"]["statistics"]["summary"]
|
|
354
|
+
@duration = @stats_summary["duration"]
|
|
355
|
+
@instances = @stats_summary["instances"]
|
|
356
|
+
@total_instances = @instances["total"]
|
|
357
|
+
@executed = @instances["executed"]
|
|
358
|
+
@timeouts = @instances["timeouts"]
|
|
359
|
+
@errors = @instances["errors"]
|
|
360
|
+
@asserts_failed = @stats_summary["asserts"]["failed"]
|
|
361
|
+
@server = @stats_summary["server"]
|
|
362
|
+
@server_tx_bytes = @server["tx"]["bytes"]
|
|
363
|
+
@server_tx_msgs = @server["tx"]["msgs"]
|
|
364
|
+
@server_rx_bytes = @server["rx"]["bytes"]
|
|
365
|
+
@server_rx_msgs = @server["rx"]["msgs"]
|
|
366
|
+
@client = @stats_summary["client"]
|
|
367
|
+
@client_tx_bytes = @client["tx"]["bytes"]
|
|
368
|
+
@client_tx_msgs = @client["tx"]["msgs"]
|
|
369
|
+
@client_rx_bytes = @client["rx"]["bytes"]
|
|
370
|
+
@client_rx_msgs = @client["rx"]["msgs"]
|
|
371
|
+
@scenarios = status["status"]["statistics"]["scenarios"]
|
|
372
|
+
@scenarios.each do | scenario |
|
|
373
|
+
@reported_volume = @reported_volume + scenario["volume"]
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
msg ""
|
|
377
|
+
msg "duration: #{format_float(2, @duration)}"
|
|
378
|
+
msg "concurrency: #{@reported_volume}"
|
|
379
|
+
msg "tests/sec: #{format_float(2, @executed.to_f / @duration)}" if @duration.to_i > 0
|
|
380
|
+
msg "passed: #{@executed}"
|
|
381
|
+
msg "errors: #{@errors}"
|
|
382
|
+
msg "timeouts: #{@timeouts}"
|
|
383
|
+
msg "client tx bytes/sec #{format_float(2, @client_tx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
|
384
|
+
msg "client tx msgs/sec #{format_float(2, @client_tx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
|
385
|
+
msg "client rx bytes/sec #{format_float(2, @client_rx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
|
386
|
+
msg "client rx msgs/sec #{format_float(2, @client_rx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
|
387
|
+
msg "server tx bytes/sec #{format_float(2, @server_tx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
|
388
|
+
msg "server tx msgs/sec #{format_float(2, @server_tx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
|
389
|
+
msg "server rx bytes/sec #{format_float(2, @server_rx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
|
390
|
+
msg "server rx msgs/sec #{format_float(2, @server_rx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
|
391
|
+
msg ""
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def parse_cli argv
|
|
395
|
+
@hash = Hash.new
|
|
396
|
+
while not argv.empty?
|
|
397
|
+
break if argv.first[0,1] != '-'
|
|
398
|
+
|
|
399
|
+
k = argv.shift
|
|
400
|
+
|
|
401
|
+
if [ '-c', '--csv' ].member? k
|
|
402
|
+
@hash['testset'] = shift(k, argv)
|
|
403
|
+
next
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
if [ '-d', '--dir' ].member? k
|
|
407
|
+
@hash['dir'] = shift(k, argv)
|
|
408
|
+
next
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
if [ '-f', '--default_host' ].member? k
|
|
412
|
+
@hash['default_host'] = shift(k, argv)
|
|
413
|
+
next
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
if [ '-i', '--interfaces' ].member? k
|
|
417
|
+
@hash['interfaces'] = shift(k, argv)
|
|
418
|
+
next
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
if [ '-h', '--help' ].member? k
|
|
422
|
+
help
|
|
423
|
+
exit
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
if [ '-m', '--mu_string' ].member? k
|
|
427
|
+
mu_string = shift(k, argv)
|
|
428
|
+
if mu_string =~ /(.+?):(.+?)@(.*)/
|
|
429
|
+
@@mu_admin_user = $1
|
|
430
|
+
@@mu_admin_pass = $2
|
|
431
|
+
@@mu_ip = $3
|
|
432
|
+
end
|
|
433
|
+
next
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
if [ '-o', '--output' ].member? k
|
|
437
|
+
$stdout.reopen(shift(k, argv), "w")
|
|
438
|
+
next
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
if [ '-p', '--pattern' ].member? k
|
|
442
|
+
patterns = Array.new
|
|
443
|
+
pattern_string = shift(k, argv)
|
|
444
|
+
pstrings = pattern_string.split(",")
|
|
445
|
+
pstrings.each do | p |
|
|
446
|
+
if p =~ /(.+?)-(.+?):(.*)/ # e.g. 1-10000:60
|
|
447
|
+
start_vol = $1
|
|
448
|
+
end_vol = $2
|
|
449
|
+
duration = $3
|
|
450
|
+
patterns << "{\"iterations\":1, \"end\":#{end_vol}, \"start\":#{start_vol}, \"duration\":#{duration} }"
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
ps = "{ \"iterations\": 1, \"intervals\": ["
|
|
454
|
+
patterns.each do | p |
|
|
455
|
+
ps = ps + p + ","
|
|
456
|
+
end
|
|
457
|
+
ps = ps[0..ps.length-2] # remove final comma
|
|
458
|
+
ps = ps + "] }"
|
|
459
|
+
@hash['pattern'] = ps
|
|
460
|
+
next
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if [ '-r', '--recursive'].member? k
|
|
464
|
+
@hash['recursive'] = true
|
|
465
|
+
next
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
if [ '-s', '--scenario' ].member? k
|
|
470
|
+
@hash['scenario'] = shift(k, argv)
|
|
471
|
+
next
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
if [ '-t', '--test' ].member? k
|
|
475
|
+
@hash['test'] = true
|
|
476
|
+
next
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
if [ '-v', '--verbose' ].member? k
|
|
480
|
+
$log.level = Logger::DEBUG
|
|
481
|
+
next
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
raise ArgumentError, "Unknown option #{k}"
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
@hash
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def help
|
|
491
|
+
helps = [
|
|
492
|
+
{ :short => '-c', :long => '--csv', :value => '<string>', :help => 'name of the csv testset to run' },
|
|
493
|
+
{ :short => '-d', :long => '--dir', :value => '<string>', :help => 'directory containing the scenario file' },
|
|
494
|
+
{ :short => '-f', :long => '--default_host', :value => '<string>', :help => 'default_host setting' },
|
|
495
|
+
{ :short => '-h', :long => '--help', :value => '', :help => 'help on command line options' },
|
|
496
|
+
{ :short => '-i', :long => '--interfaces', :value => '<string>', :help => 'comma-separated list of interfaces, e.g. b1,b2 or b1-1000,b2 for ip range' },
|
|
497
|
+
{ :short => '-m', :long => '--mu_string', :value => '<string>', :help => 'user, password, mu_ip in the form of admin:admin@10.9.8.7' },
|
|
498
|
+
{ :short => '-o', :long => '--output', :value => '<string>', :help => 'output logging to this file' },
|
|
499
|
+
{ :short => '-p', :long => '--pattern', :value => '<string>', :help => 'pattern in the form of comma-separated concurrency_start-end:duration patterns, e.g. 1-100:60,100-100:60,100-1:60' },
|
|
500
|
+
{ :short => '-r', :long => '--recursive', :value => '', :help => 'for run_dir, recurse through sub-directories' },
|
|
501
|
+
{ :short => '-s', :long => '--scenario', :value => '<string>', :help => 'scenario file to run' },
|
|
502
|
+
{ :short => '-t', :long => '--test', :value => '', :help => 'do verify only' },
|
|
503
|
+
{ :short => '-v', :long => '--verbose', :value => '', :help => 'set Logger::DEBUG level' }
|
|
504
|
+
]
|
|
505
|
+
|
|
506
|
+
cmds = [
|
|
507
|
+
"mu cmd_runscale:help",
|
|
508
|
+
"mu cmd_runscale:run_file -s <scenario> -i <hosts, e.g. a1,dell-9> -p <pattern, e.g. 1-1000:30>",
|
|
509
|
+
"mu cmd_runscale:run_dir -d <scenario_directory>",
|
|
510
|
+
"mu cmd_runscale:running?"
|
|
511
|
+
]
|
|
512
|
+
|
|
513
|
+
max_long_size = helps.inject(0) { |memo, obj| [ obj[:long].size, memo ].max }
|
|
514
|
+
max_value_size = helps.inject(0) { |memo, obj| [ obj[:value].size, memo ].max }
|
|
515
|
+
puts
|
|
516
|
+
puts "Usage: mu cmd_runscale:<command> <options>"
|
|
517
|
+
puts
|
|
518
|
+
helps.each do |h|
|
|
519
|
+
puts "%-*s %*s %-*s %s" % [max_long_size, h[:long], 2, h[:short], max_value_size, h[:value], h[:help]]
|
|
520
|
+
end
|
|
521
|
+
puts
|
|
522
|
+
puts "Available Commands"
|
|
523
|
+
puts
|
|
524
|
+
cmds.each do | c |
|
|
525
|
+
puts c
|
|
526
|
+
end
|
|
527
|
+
puts
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
end
|
|
531
|
+
end # Command
|
|
532
|
+
end # Mu
|
|
533
|
+
|