bosh_cli 0.18 → 0.19
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/bin/bosh +7 -4
- data/lib/cli.rb +2 -0
- data/lib/cli/commands/base.rb +1 -18
- data/lib/cli/commands/biff.rb +3 -7
- data/lib/cli/commands/cloudcheck.rb +1 -0
- data/lib/cli/commands/deployment.rb +10 -6
- data/lib/cli/commands/job_rename.rb +116 -0
- data/lib/cli/commands/stemcell.rb +3 -3
- data/lib/cli/commands/task.rb +52 -143
- data/lib/cli/commands/vms.rb +2 -2
- data/lib/cli/config.rb +1 -0
- data/lib/cli/deployment_helper.rb +62 -22
- data/lib/cli/director.rb +85 -196
- data/lib/cli/director_task.rb +4 -4
- data/lib/cli/event_log_renderer.rb +5 -1
- data/lib/cli/null_renderer.rb +19 -0
- data/lib/cli/package_builder.rb +91 -62
- data/lib/cli/packaging_helper.rb +1 -1
- data/lib/cli/release_builder.rb +47 -13
- data/lib/cli/runner.rb +21 -39
- data/lib/cli/task_log_renderer.rb +9 -0
- data/lib/cli/task_tracker.rb +168 -0
- data/lib/cli/templates/help_message.erb +1 -0
- data/lib/cli/version.rb +1 -1
- data/lib/cli/versions_index.rb +3 -3
- data/spec/unit/biff_spec.rb +5 -0
- data/spec/unit/director_spec.rb +96 -192
- data/spec/unit/job_rename_spec.rb +195 -0
- data/spec/unit/package_builder_spec.rb +188 -186
- data/spec/unit/release_builder_spec.rb +27 -9
- data/spec/unit/runner_spec.rb +0 -25
- data/spec/unit/task_tracker_spec.rb +154 -0
- metadata +11 -4
@@ -6,6 +6,10 @@ module Bosh::Cli
|
|
6
6
|
def self.create_for_log_type(log_type)
|
7
7
|
if log_type == "event"
|
8
8
|
EventLogRenderer.new
|
9
|
+
elsif log_type == "result"
|
10
|
+
# Null renderer doesn't output anything to screen, so it fits well
|
11
|
+
# in case we need to fetch task result log only, without rendering it
|
12
|
+
NullRenderer.new
|
9
13
|
else
|
10
14
|
TaskLogRenderer.new
|
11
15
|
end
|
@@ -20,6 +24,11 @@ module Bosh::Cli
|
|
20
24
|
@lock = Mutex.new
|
21
25
|
@output = ""
|
22
26
|
@time_adjustment = 0
|
27
|
+
@duration = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def duration_known?
|
31
|
+
false # TODO: make it available for basic renderer
|
23
32
|
end
|
24
33
|
|
25
34
|
def add_output(output)
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh
|
4
|
+
module Cli
|
5
|
+
# This class is responsible for tracking director tasks
|
6
|
+
class TaskTracker
|
7
|
+
|
8
|
+
MAX_POLLS = nil # not limited
|
9
|
+
POLL_INTERVAL = 1 # second
|
10
|
+
|
11
|
+
attr_reader :output
|
12
|
+
|
13
|
+
# @param [Bosh::Cli::Director] director
|
14
|
+
# @param [Integer] task_id
|
15
|
+
# @param [Hash] options
|
16
|
+
def initialize(director, task_id, options = {})
|
17
|
+
@director = director
|
18
|
+
@task_id = task_id
|
19
|
+
@options = options
|
20
|
+
|
21
|
+
@log_type = options[:log_type] || "event"
|
22
|
+
@use_cache = options.key?(:use_cache) ? @options[:use_cache] : true
|
23
|
+
|
24
|
+
@output = nil
|
25
|
+
@cache = Config.cache
|
26
|
+
@task = Bosh::Cli::DirectorTask.new(@director, @task_id, @log_type)
|
27
|
+
|
28
|
+
if options[:raw_output]
|
29
|
+
@renderer = Bosh::Cli::TaskLogRenderer.new
|
30
|
+
else
|
31
|
+
@renderer = Bosh::Cli::TaskLogRenderer.create_for_log_type(@log_type)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Tracks director task. Blocks until task is in one of the 'finished'
|
36
|
+
# states (done, error, cancelled). Handles Ctrl+C by prompting to cancel
|
37
|
+
# task.
|
38
|
+
# @return [Symbol] Task status
|
39
|
+
def track
|
40
|
+
nl
|
41
|
+
@renderer.time_adjustment = @director.get_time_difference
|
42
|
+
say("Director task #{@task_id.to_s.yellow}")
|
43
|
+
|
44
|
+
cached_output = get_cached_task_output
|
45
|
+
|
46
|
+
if cached_output
|
47
|
+
task_status = @task.state.to_sym
|
48
|
+
output_received(cached_output)
|
49
|
+
@renderer.refresh
|
50
|
+
else
|
51
|
+
task_status = poll
|
52
|
+
end
|
53
|
+
|
54
|
+
if task_status == :error && interactive? && @log_type != "debug"
|
55
|
+
prompt_for_debug_log
|
56
|
+
else
|
57
|
+
print_task_summary(task_status)
|
58
|
+
end
|
59
|
+
|
60
|
+
save_task_output unless cached_output
|
61
|
+
task_status
|
62
|
+
end
|
63
|
+
|
64
|
+
def poll
|
65
|
+
polls = 0
|
66
|
+
|
67
|
+
while true
|
68
|
+
polls += 1
|
69
|
+
state = @task.state
|
70
|
+
output = @task.output
|
71
|
+
|
72
|
+
output_received(output)
|
73
|
+
@renderer.refresh
|
74
|
+
|
75
|
+
if finished?(state)
|
76
|
+
return state.to_sym
|
77
|
+
elsif MAX_POLLS && polls >= MAX_POLLS
|
78
|
+
return :track_timeout
|
79
|
+
end
|
80
|
+
|
81
|
+
sleep(POLL_INTERVAL)
|
82
|
+
end
|
83
|
+
|
84
|
+
:unknown
|
85
|
+
rescue Interrupt # Local ctrl-c handler
|
86
|
+
prompt_for_task_cancel
|
87
|
+
end
|
88
|
+
|
89
|
+
def prompt_for_debug_log
|
90
|
+
return unless interactive?
|
91
|
+
nl
|
92
|
+
confirm = ask("The task has returned an error status, " +
|
93
|
+
"do you want to see debug log? [Yn]: ")
|
94
|
+
if confirm.empty? || confirm =~ /y(es)?/i
|
95
|
+
self.class.new(@director, @task_id,
|
96
|
+
@options.merge(:log_type => "debug")).track
|
97
|
+
else
|
98
|
+
say("Please use 'bosh task #{@task_id}' command ".red +
|
99
|
+
"to see the debug log".red)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def prompt_for_task_cancel
|
104
|
+
return unless interactive?
|
105
|
+
nl
|
106
|
+
confirm = ask("Do you want to cancel task #{@task_id}? [yN] " +
|
107
|
+
"(^C again to detach): ")
|
108
|
+
|
109
|
+
if confirm =~ /y(es)?/i
|
110
|
+
say("Cancelling task #{@task_id}...")
|
111
|
+
@director.cancel_task(@task_id)
|
112
|
+
end
|
113
|
+
|
114
|
+
poll
|
115
|
+
rescue Interrupt
|
116
|
+
nl
|
117
|
+
err("Task #{@task_id} is still running")
|
118
|
+
end
|
119
|
+
|
120
|
+
def print_task_summary(task_status)
|
121
|
+
output_received(@task.flush_output)
|
122
|
+
@renderer.finish(task_status)
|
123
|
+
|
124
|
+
nl
|
125
|
+
say("Task #{@task_id} #{task_status.to_s.yellow}")
|
126
|
+
|
127
|
+
if task_status == :done && @renderer.duration_known?
|
128
|
+
say("Started\t\t#{@renderer.started_at.utc.to_s}")
|
129
|
+
say("Finished\t#{@renderer.finished_at.utc.to_s}")
|
130
|
+
say("Duration\t#{format_time(@renderer.duration).yellow}")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# @param [String] output Output received from director task
|
137
|
+
def output_received(output)
|
138
|
+
return if output.nil?
|
139
|
+
@output ||= ""
|
140
|
+
@output << output
|
141
|
+
@renderer.add_output(output)
|
142
|
+
end
|
143
|
+
|
144
|
+
def finished?(state)
|
145
|
+
%(done error cancelled).include?(state)
|
146
|
+
end
|
147
|
+
|
148
|
+
def interactive?
|
149
|
+
Bosh::Cli::Config.interactive
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_cached_task_output
|
153
|
+
return nil unless @use_cache
|
154
|
+
@cache.read(task_cache_key)
|
155
|
+
end
|
156
|
+
|
157
|
+
def save_task_output
|
158
|
+
return nil unless @output && @use_cache
|
159
|
+
@cache.write(task_cache_key, @output)
|
160
|
+
end
|
161
|
+
|
162
|
+
def task_cache_key
|
163
|
+
"task/#{@director.uuid}/#{@task_id}/#{@log_type}"
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/cli/version.rb
CHANGED
data/lib/cli/versions_index.rb
CHANGED
@@ -38,14 +38,14 @@ module Bosh::Cli
|
|
38
38
|
|
39
39
|
return nil if builds.empty?
|
40
40
|
|
41
|
-
sorted = builds.sort
|
41
|
+
sorted = builds.sort do |build1, build2|
|
42
42
|
cmp = version_cmp(build2["version"], build1["version"])
|
43
43
|
if cmp == 0
|
44
44
|
raise "There is a duplicate version `#{build1["version"]}' " +
|
45
|
-
|
45
|
+
"in index `#{@index_file}'"
|
46
46
|
end
|
47
47
|
cmp
|
48
|
-
|
48
|
+
end
|
49
49
|
|
50
50
|
sorted[0]["version"]
|
51
51
|
end
|
data/spec/unit/biff_spec.rb
CHANGED
@@ -110,6 +110,11 @@ describe Bosh::Cli::Command::Biff do
|
|
110
110
|
@biff.find_in("path1.path2.path3", obj).should == 3
|
111
111
|
end
|
112
112
|
|
113
|
+
it "finds the object(boolean) path" do
|
114
|
+
obj = { "path1" => {"path2" => {"path3" => false}} }
|
115
|
+
@biff.find_in("path1.path2.path3", obj).should == false
|
116
|
+
end
|
117
|
+
|
113
118
|
it "finds the object path in an array by the name key" do
|
114
119
|
obj = { "by_key" => 1, "arr" => [{"name" => "by_name"}]}
|
115
120
|
@biff.find_in("arr.by_name", obj).should == {"name" => "by_name"}
|
data/spec/unit/director_spec.rb
CHANGED
@@ -13,30 +13,30 @@ describe Bosh::Cli::Director do
|
|
13
13
|
describe "fetching status" do
|
14
14
|
it "tells if user is authenticated" do
|
15
15
|
@director.should_receive(:get).with("/info", "application/json").
|
16
|
-
|
16
|
+
and_return([200, JSON.generate("user" => "adam")])
|
17
17
|
@director.authenticated?.should == true
|
18
18
|
end
|
19
19
|
|
20
20
|
it "tells if user not authenticated" do
|
21
21
|
@director.should_receive(:get).with("/info", "application/json").
|
22
|
-
|
22
|
+
and_return([403, "Forbidden"])
|
23
23
|
@director.authenticated?.should == false
|
24
24
|
|
25
25
|
@director.should_receive(:get).with("/info", "application/json").
|
26
|
-
|
26
|
+
and_return([500, "Error"])
|
27
27
|
@director.authenticated?.should == false
|
28
28
|
|
29
29
|
@director.should_receive(:get).with("/info", "application/json").
|
30
|
-
|
30
|
+
and_return([404, "Not Found"])
|
31
31
|
@director.authenticated?.should == false
|
32
32
|
|
33
33
|
@director.should_receive(:get).with("/info", "application/json").
|
34
|
-
|
34
|
+
and_return([200, JSON.generate("user" => nil, "version" => 1)])
|
35
35
|
@director.authenticated?.should == false
|
36
36
|
|
37
37
|
# Backward compatibility
|
38
38
|
@director.should_receive(:get).with("/info", "application/json").
|
39
|
-
|
39
|
+
and_return([200, JSON.generate("status" => "ZB")])
|
40
40
|
@director.authenticated?.should == true
|
41
41
|
end
|
42
42
|
end
|
@@ -53,157 +53,144 @@ describe Bosh::Cli::Director do
|
|
53
53
|
describe "API calls" do
|
54
54
|
it "creates user" do
|
55
55
|
@director.should_receive(:post).
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
with("/users", "application/json",
|
57
|
+
JSON.generate("username" => "joe", "password" => "pass")).
|
58
|
+
and_return(true)
|
59
59
|
@director.create_user("joe", "pass")
|
60
60
|
end
|
61
61
|
|
62
62
|
it "uploads stemcell" do
|
63
63
|
@director.should_receive(:upload_and_track).
|
64
|
-
|
65
|
-
|
64
|
+
with("/stemcells", "application/x-compressed", "/path").
|
65
|
+
and_return(true)
|
66
66
|
@director.upload_stemcell("/path")
|
67
67
|
end
|
68
68
|
|
69
69
|
it "lists stemcells" do
|
70
70
|
@director.should_receive(:get).with("/stemcells", "application/json").
|
71
|
-
|
71
|
+
and_return([200, JSON.generate([]), {}])
|
72
72
|
@director.list_stemcells
|
73
73
|
end
|
74
74
|
|
75
75
|
it "lists releases" do
|
76
76
|
@director.should_receive(:get).with("/releases", "application/json").
|
77
|
-
|
77
|
+
and_return([200, JSON.generate([]), {}])
|
78
78
|
@director.list_releases
|
79
79
|
end
|
80
80
|
|
81
81
|
it "lists deployments" do
|
82
82
|
@director.should_receive(:get).with("/deployments", "application/json").
|
83
|
-
|
83
|
+
and_return([200, JSON.generate([]), {}])
|
84
84
|
@director.list_deployments
|
85
85
|
end
|
86
86
|
|
87
87
|
it "lists currently running tasks (director version < 0.3.5)" do
|
88
88
|
@director.should_receive(:get).with("/info", "application/json").
|
89
|
-
|
89
|
+
and_return([200, JSON.generate({ :version => "0.3.2"})])
|
90
90
|
@director.should_receive(:get).
|
91
|
-
|
92
|
-
|
91
|
+
with("/tasks?state=processing", "application/json").
|
92
|
+
and_return([200, JSON.generate([]), {}])
|
93
93
|
@director.list_running_tasks
|
94
94
|
end
|
95
95
|
|
96
96
|
it "lists currently running tasks (director version >= 0.3.5)" do
|
97
97
|
@director.should_receive(:get).
|
98
|
-
|
99
|
-
|
98
|
+
with("/info", "application/json").
|
99
|
+
and_return([200, JSON.generate({ :version => "0.3.5"})])
|
100
100
|
@director.should_receive(:get).
|
101
|
-
|
102
|
-
|
101
|
+
with("/tasks?state=processing,cancelling,queued", "application/json").
|
102
|
+
and_return([200, JSON.generate([]), {}])
|
103
103
|
@director.list_running_tasks
|
104
104
|
end
|
105
105
|
|
106
106
|
it "lists recent tasks" do
|
107
107
|
@director.should_receive(:get).
|
108
|
-
|
109
|
-
|
108
|
+
with("/tasks?limit=30", "application/json").
|
109
|
+
and_return([200, JSON.generate([]), {}])
|
110
110
|
@director.list_recent_tasks
|
111
111
|
|
112
112
|
@director.should_receive(:get).
|
113
|
-
|
114
|
-
|
113
|
+
with("/tasks?limit=100", "application/json").
|
114
|
+
and_return([200, JSON.generate([]), {}])
|
115
115
|
@director.list_recent_tasks(100000)
|
116
116
|
end
|
117
117
|
|
118
118
|
it "uploads release" do
|
119
119
|
@director.should_receive(:upload_and_track).
|
120
|
-
|
121
|
-
|
122
|
-
and_return(true)
|
120
|
+
with("/releases", "application/x-compressed", "/path").
|
121
|
+
and_return(true)
|
123
122
|
@director.upload_release("/path")
|
124
123
|
end
|
125
124
|
|
126
125
|
it "gets release info" do
|
127
126
|
@director.should_receive(:get).
|
128
|
-
|
129
|
-
|
127
|
+
with("/releases/foo", "application/json").
|
128
|
+
and_return([200, JSON.generate([]), { }])
|
130
129
|
@director.get_release("foo")
|
131
130
|
end
|
132
131
|
|
133
132
|
it "gets deployment info" do
|
134
133
|
@director.should_receive(:get).
|
135
|
-
|
136
|
-
|
134
|
+
with("/deployments/foo", "application/json").
|
135
|
+
and_return([200, JSON.generate([]), { }])
|
137
136
|
@director.get_deployment("foo")
|
138
137
|
end
|
139
138
|
|
140
139
|
it "deletes stemcell" do
|
141
140
|
@director.should_receive(:request_and_track).
|
142
|
-
|
143
|
-
nil, nil, :log_type => "event").
|
144
|
-
and_return(true)
|
141
|
+
with(:delete, "/stemcells/ubuntu/123").and_return(true)
|
145
142
|
@director.delete_stemcell("ubuntu", "123")
|
146
143
|
end
|
147
144
|
|
148
145
|
it "deletes deployment" do
|
149
146
|
@director.should_receive(:request_and_track).
|
150
|
-
|
151
|
-
nil, nil, :log_type => "event").
|
152
|
-
and_return(true)
|
147
|
+
with(:delete, "/deployments/foo").and_return(true)
|
153
148
|
@director.delete_deployment("foo")
|
154
149
|
end
|
155
150
|
|
156
151
|
it "deletes release (non-force)" do
|
157
152
|
@director.should_receive(:request_and_track).
|
158
|
-
|
159
|
-
nil, nil, :log_type => "event").
|
160
|
-
and_return(true)
|
153
|
+
with(:delete, "/releases/za").and_return(true)
|
161
154
|
@director.delete_release("za")
|
162
155
|
end
|
163
156
|
|
164
157
|
it "deletes release (force)" do
|
165
158
|
@director.should_receive(:request_and_track).
|
166
|
-
|
167
|
-
nil, nil, :log_type => "event").
|
168
|
-
and_return(true)
|
159
|
+
with(:delete, "/releases/zb?force=true").and_return(true)
|
169
160
|
@director.delete_release("zb", :force => true)
|
170
161
|
end
|
171
162
|
|
172
163
|
it "deploys" do
|
173
164
|
@director.should_receive(:request_and_track).
|
174
|
-
|
175
|
-
"manifest", :log_type => "event").
|
176
|
-
and_return(true)
|
165
|
+
with(:post, "/deployments", "text/yaml", "manifest").and_return(true)
|
177
166
|
@director.deploy("manifest")
|
178
167
|
end
|
179
168
|
|
180
169
|
it "changes job state" do
|
181
170
|
@director.should_receive(:request_and_track).
|
182
|
-
|
183
|
-
|
184
|
-
and_return(true)
|
171
|
+
with(:put, "/deployments/foo/jobs/dea?state=stopped",
|
172
|
+
"text/yaml", "manifest").and_return(true)
|
185
173
|
@director.change_job_state("foo", "manifest", "dea", nil, "stopped")
|
186
174
|
end
|
187
175
|
|
188
176
|
it "changes job instance state" do
|
189
177
|
@director.should_receive(:request_and_track).
|
190
|
-
|
191
|
-
|
192
|
-
and_return(true)
|
178
|
+
with(:put, "/deployments/foo/jobs/dea/0?state=detached",
|
179
|
+
"text/yaml", "manifest").and_return(true)
|
193
180
|
@director.change_job_state("foo", "manifest", "dea", 0, "detached")
|
194
181
|
end
|
195
182
|
|
196
183
|
it "gets task state" do
|
197
184
|
@director.should_receive(:get).
|
198
|
-
|
199
|
-
|
185
|
+
with("/tasks/232").
|
186
|
+
and_return([200, JSON.generate({ "state" => "done" })])
|
200
187
|
@director.get_task_state(232).should == "done"
|
201
188
|
end
|
202
189
|
|
203
190
|
it "whines on missing task" do
|
204
191
|
@director.should_receive(:get).
|
205
|
-
|
206
|
-
|
192
|
+
with("/tasks/232").
|
193
|
+
and_return([404, "Not Found"])
|
207
194
|
lambda {
|
208
195
|
@director.get_task_state(232).should
|
209
196
|
}.should raise_error(Bosh::Cli::MissingTask)
|
@@ -211,17 +198,17 @@ describe Bosh::Cli::Director do
|
|
211
198
|
|
212
199
|
it "gets task output" do
|
213
200
|
@director.should_receive(:get).
|
214
|
-
|
215
|
-
|
216
|
-
|
201
|
+
with("/tasks/232/output", nil,
|
202
|
+
nil, { "Range" => "bytes=42-" }).
|
203
|
+
and_return([206, "test", { :content_range => "bytes 42-56/100" }])
|
217
204
|
@director.get_task_output(232, 42).should == ["test", 57]
|
218
205
|
end
|
219
206
|
|
220
207
|
it "doesn't set task output new offset if it wasn't a partial response" do
|
221
208
|
@director.should_receive(:get).
|
222
|
-
|
223
|
-
|
224
|
-
|
209
|
+
with("/tasks/232/output", nil, nil,
|
210
|
+
{ "Range" => "bytes=42-" }).
|
211
|
+
and_return([200, "test"])
|
225
212
|
@director.get_task_output(232, 42).should == ["test", nil]
|
226
213
|
end
|
227
214
|
|
@@ -231,8 +218,8 @@ describe Bosh::Cli::Director do
|
|
231
218
|
Time.stub!(:now).and_return(now)
|
232
219
|
|
233
220
|
@director.should_receive(:get).with("/info").
|
234
|
-
|
235
|
-
|
221
|
+
and_return([200, JSON.generate("version" => 1),
|
222
|
+
{ :date => server_time.rfc822 }])
|
236
223
|
@director.get_time_difference.to_i.should == 100
|
237
224
|
end
|
238
225
|
|
@@ -241,59 +228,65 @@ describe Bosh::Cli::Director do
|
|
241
228
|
describe "checking status" do
|
242
229
|
it "considers target valid if it responds with 401 (for compatibility)" do
|
243
230
|
@director.stub(:get).
|
244
|
-
|
245
|
-
|
231
|
+
with("/info", "application/json").
|
232
|
+
and_return([401, "Not authorized"])
|
246
233
|
@director.exists?.should be_true
|
247
234
|
end
|
248
235
|
|
249
236
|
it "considers target valid if it responds with 200" do
|
250
237
|
@director.stub(:get).
|
251
|
-
|
252
|
-
|
238
|
+
with("/info", "application/json").
|
239
|
+
and_return([200, JSON.generate("name" => "Director is your friend")])
|
253
240
|
@director.exists?.should be_true
|
254
241
|
end
|
255
242
|
end
|
256
243
|
|
257
244
|
describe "tracking request" do
|
258
245
|
it "starts polling task if request responded with a redirect to task URL" do
|
246
|
+
options = { :arg1 => 1, :arg2 => 2 }
|
247
|
+
|
259
248
|
@director.should_receive(:request).
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
249
|
+
with(:get, "/stuff", "text/plain", "abc").
|
250
|
+
and_return([302, "body", { :location => "/tasks/502" }])
|
251
|
+
|
252
|
+
tracker = mock("tracker", :track => "polling result", :output => "foo")
|
253
|
+
|
254
|
+
Bosh::Cli::TaskTracker.should_receive(:new).
|
255
|
+
with(@director, "502", options).
|
256
|
+
and_return(tracker)
|
257
|
+
|
265
258
|
@director.request_and_track(:get, "/stuff", "text/plain",
|
266
|
-
"abc",
|
267
|
-
|
259
|
+
"abc", options).
|
260
|
+
should == ["polling result", "502", "foo"]
|
268
261
|
end
|
269
262
|
|
270
|
-
it "considers all
|
263
|
+
it "considers all responses but 302 a failure" do
|
271
264
|
[200, 404, 403].each do |code|
|
272
265
|
@director.should_receive(:request).
|
273
|
-
|
274
|
-
|
266
|
+
with(:get, "/stuff", "text/plain", "abc").
|
267
|
+
and_return([code, "body", {}])
|
275
268
|
@director.request_and_track(:get, "/stuff", "text/plain",
|
276
269
|
"abc", :arg1 => 1, :arg2 => 2).
|
277
|
-
|
270
|
+
should == [:failed, nil, nil]
|
278
271
|
end
|
279
272
|
end
|
280
273
|
|
281
|
-
it "reports task as non
|
274
|
+
it "reports task as non-trackable if its URL is unfamiliar" do
|
282
275
|
@director.should_receive(:request).
|
283
|
-
|
284
|
-
|
276
|
+
with(:get, "/stuff", "text/plain", "abc").
|
277
|
+
and_return([302, "body", { :location => "/track-task/502" }])
|
285
278
|
@director.request_and_track(:get, "/stuff", "text/plain",
|
286
279
|
"abc", :arg1 => 1, :arg2 => 2).
|
287
|
-
|
280
|
+
should == [:non_trackable, nil, nil]
|
288
281
|
end
|
289
282
|
|
290
|
-
it "
|
283
|
+
it "supports uploading with progress bar" do
|
291
284
|
file = spec_asset("valid_release.tgz")
|
292
285
|
f = Bosh::Cli::FileWithProgressBar.open(file, "r")
|
293
286
|
|
294
287
|
Bosh::Cli::FileWithProgressBar.stub!(:open).with(file, "r").and_return(f)
|
295
288
|
@director.should_receive(:request_and_track).
|
296
|
-
|
289
|
+
with(:post, "/stuff", "application/x-compressed", f, { })
|
297
290
|
@director.upload_and_track("/stuff", "application/x-compressed", file)
|
298
291
|
f.progress_bar.finished?.should be_true
|
299
292
|
end
|
@@ -308,16 +301,16 @@ describe Bosh::Cli::Director do
|
|
308
301
|
|
309
302
|
client = mock("httpclient")
|
310
303
|
client.should_receive(:send_timeout=).
|
311
|
-
|
304
|
+
with(Bosh::Cli::Director::API_TIMEOUT)
|
312
305
|
client.should_receive(:receive_timeout=).
|
313
|
-
|
306
|
+
with(Bosh::Cli::Director::API_TIMEOUT)
|
314
307
|
client.should_receive(:connect_timeout=).
|
315
|
-
|
308
|
+
with(Bosh::Cli::Director::CONNECT_TIMEOUT)
|
316
309
|
HTTPClient.stub!(:new).and_return(client)
|
317
310
|
|
318
311
|
client.should_receive(:request).
|
319
|
-
|
320
|
-
|
312
|
+
with(:get, "http://target/stuff", :body => "payload",
|
313
|
+
:header => headers.merge("Authorization" => auth))
|
321
314
|
@director.send(:perform_http_request, :get,
|
322
315
|
"http://target/stuff", "payload", headers)
|
323
316
|
end
|
@@ -351,7 +344,7 @@ describe Bosh::Cli::Director do
|
|
351
344
|
:headers => {})
|
352
345
|
|
353
346
|
@director.should_receive(:perform_http_request).
|
354
|
-
|
347
|
+
and_return(mock_response)
|
355
348
|
@director.request(:get, "/stuff", "application/octet-stream",
|
356
349
|
"payload", { :hdr1 => "a", :hdr2 => "b"})
|
357
350
|
}.should raise_error(Bosh::Cli::DirectorError,
|
@@ -363,7 +356,7 @@ describe Bosh::Cli::Director do
|
|
363
356
|
:body => "error message goes here",
|
364
357
|
:headers => {})
|
365
358
|
@director.should_receive(:perform_http_request).
|
366
|
-
|
359
|
+
and_return(mock_response)
|
367
360
|
@director.request(:get, "/stuff", "application/octet-stream",
|
368
361
|
"payload", { :hdr1 => "a", :hdr2 => "b"})
|
369
362
|
}.should raise_error(Bosh::Cli::DirectorError,
|
@@ -376,7 +369,7 @@ describe Bosh::Cli::Director do
|
|
376
369
|
:body => '{"c":"d","a":"b"}',
|
377
370
|
:headers => {})
|
378
371
|
@director.should_receive(:perform_http_request).
|
379
|
-
|
372
|
+
and_return(mock_response)
|
380
373
|
@director.request(:get, "/stuff", "application/octet-stream",
|
381
374
|
"payload", { :hdr1 => "a", :hdr2 => "b"})
|
382
375
|
}.should raise_error(Bosh::Cli::DirectorError,
|
@@ -388,13 +381,15 @@ describe Bosh::Cli::Director do
|
|
388
381
|
it "wraps director access exceptions" do
|
389
382
|
[URI::Error, SocketError, Errno::ECONNREFUSED].each do |err|
|
390
383
|
@director.should_receive(:perform_http_request).
|
391
|
-
|
384
|
+
and_raise(err.new("err message"))
|
392
385
|
lambda {
|
393
386
|
@director.request(:get, "/stuff", "app/zb", "payload", { })
|
394
387
|
}.should raise_error(Bosh::Cli::DirectorInaccessible)
|
395
388
|
end
|
389
|
+
|
396
390
|
@director.should_receive(:perform_http_request).
|
397
|
-
|
391
|
+
and_raise(SystemCallError.new("err message", 22))
|
392
|
+
|
398
393
|
lambda {
|
399
394
|
@director.request(:get, "/stuff", "app/zb", "payload", { })
|
400
395
|
}.should raise_error Bosh::Cli::DirectorError
|
@@ -404,11 +399,12 @@ describe Bosh::Cli::Director do
|
|
404
399
|
mock_response = mock("response", :code => 200,
|
405
400
|
:body => "test body", :headers => { })
|
406
401
|
@director.should_receive(:perform_http_request).
|
407
|
-
|
402
|
+
and_yield("test body").and_return(mock_response)
|
408
403
|
|
409
|
-
code, filename, headers =
|
410
|
-
|
411
|
-
|
404
|
+
code, filename, headers =
|
405
|
+
@director.request(:get,
|
406
|
+
"/files/foo", nil, nil,
|
407
|
+
{ }, { :file => true })
|
412
408
|
|
413
409
|
code.should == 200
|
414
410
|
File.read(filename).should == "test body"
|
@@ -416,96 +412,4 @@ describe Bosh::Cli::Director do
|
|
416
412
|
end
|
417
413
|
end
|
418
414
|
|
419
|
-
describe "polling jobs" do
|
420
|
-
it "polls until success" do
|
421
|
-
n_calls = 0
|
422
|
-
|
423
|
-
@director.stub!(:get_time_difference).and_return(0)
|
424
|
-
@director.should_receive(:get).
|
425
|
-
with("/tasks/1").exactly(5).times.
|
426
|
-
and_return {
|
427
|
-
n_calls += 1;
|
428
|
-
[200,
|
429
|
-
JSON.generate("state" => n_calls == 5 ? "done" : "processing")
|
430
|
-
]
|
431
|
-
}
|
432
|
-
@director.should_receive(:get).
|
433
|
-
with("/tasks/1/output",
|
434
|
-
nil, nil, "Range" => "bytes=0-").
|
435
|
-
exactly(5).times.and_return(nil)
|
436
|
-
|
437
|
-
@director.poll_task(1, :poll_interval => 0, :max_polls => 1000).
|
438
|
-
should == :done
|
439
|
-
end
|
440
|
-
|
441
|
-
it "respects max polls setting" do
|
442
|
-
@director.stub!(:get_time_difference).and_return(0)
|
443
|
-
@director.should_receive(:get).with("/tasks/1").
|
444
|
-
exactly(10).times.
|
445
|
-
and_return [200, JSON.generate("state" => "processing")]
|
446
|
-
@director.should_receive(:get).
|
447
|
-
with("/tasks/1/output",
|
448
|
-
nil, nil, "Range" => "bytes=0-").
|
449
|
-
exactly(10).times.and_return(nil)
|
450
|
-
|
451
|
-
@director.poll_task(1, :poll_interval => 0, :max_polls => 10).
|
452
|
-
should == :track_timeout
|
453
|
-
end
|
454
|
-
|
455
|
-
it "respects poll interval setting" do
|
456
|
-
@director.stub(:get).and_return([200, "processing"])
|
457
|
-
|
458
|
-
@director.should_receive(:get).with("/tasks/1").
|
459
|
-
exactly(10).times.
|
460
|
-
and_return([200, JSON.generate("state" => "processing")])
|
461
|
-
@director.should_receive(:get).
|
462
|
-
with("/tasks/1/output", nil, nil,
|
463
|
-
"Range" => "bytes=0-").
|
464
|
-
exactly(10).times.and_return(nil)
|
465
|
-
@director.should_receive(:sleep).with(5).exactly(9).times.and_return(nil)
|
466
|
-
|
467
|
-
@director.poll_task(1, :poll_interval => 5, :max_polls => 10).
|
468
|
-
should == :track_timeout
|
469
|
-
end
|
470
|
-
|
471
|
-
it "stops polling and returns error if status is not HTTP 200" do
|
472
|
-
@director.stub!(:get_time_difference).and_return(0)
|
473
|
-
|
474
|
-
@director.should_receive(:get).
|
475
|
-
with("/tasks/1").
|
476
|
-
and_return([500, JSON.generate("state" => "processing")])
|
477
|
-
|
478
|
-
lambda {
|
479
|
-
@director.poll_task(1, :poll_interval => 0, :max_polls => 10)
|
480
|
-
}.should raise_error(Bosh::Cli::TaskTrackError,
|
481
|
-
"Got HTTP 500 while tracking task state")
|
482
|
-
end
|
483
|
-
|
484
|
-
it "stops polling and returns error if task state is error" do
|
485
|
-
@director.stub!(:get_time_difference).and_return(0)
|
486
|
-
|
487
|
-
@director.stub(:get).
|
488
|
-
with("/tasks/1/output", nil, nil,
|
489
|
-
"Range" => "bytes=0-").
|
490
|
-
and_return([200, ""])
|
491
|
-
|
492
|
-
@director.stub(:get).
|
493
|
-
with("/tasks/1").
|
494
|
-
and_return([200, JSON.generate("state" => "error")])
|
495
|
-
|
496
|
-
@director.should_receive(:get).exactly(2).times
|
497
|
-
|
498
|
-
@director.poll_task(1, :poll_interval => 0, :max_polls => 10).
|
499
|
-
should == :error
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
it "calls cancel_task on the current task when cancel_current is called" do
|
504
|
-
task_num = 1
|
505
|
-
@director.stub(:cancel_task).and_return(["body", 200])
|
506
|
-
@director.should_receive(:cancel_task).once.with(task_num)
|
507
|
-
@director.should_receive(:say).once.with("Cancelling task ##{task_num}.")
|
508
|
-
@director.current_running_task = task_num
|
509
|
-
@director.cancel_current
|
510
|
-
end
|
511
415
|
end
|