bosh_cli 0.16
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/README +4 -0
- data/Rakefile +55 -0
- data/bin/bosh +17 -0
- data/lib/cli.rb +76 -0
- data/lib/cli/cache.rb +44 -0
- data/lib/cli/changeset_helper.rb +142 -0
- data/lib/cli/command_definition.rb +52 -0
- data/lib/cli/commands/base.rb +245 -0
- data/lib/cli/commands/biff.rb +300 -0
- data/lib/cli/commands/blob.rb +125 -0
- data/lib/cli/commands/cloudcheck.rb +169 -0
- data/lib/cli/commands/deployment.rb +147 -0
- data/lib/cli/commands/job.rb +42 -0
- data/lib/cli/commands/job_management.rb +117 -0
- data/lib/cli/commands/log_management.rb +81 -0
- data/lib/cli/commands/maintenance.rb +131 -0
- data/lib/cli/commands/misc.rb +240 -0
- data/lib/cli/commands/package.rb +112 -0
- data/lib/cli/commands/property_management.rb +125 -0
- data/lib/cli/commands/release.rb +469 -0
- data/lib/cli/commands/ssh.rb +271 -0
- data/lib/cli/commands/stemcell.rb +184 -0
- data/lib/cli/commands/task.rb +213 -0
- data/lib/cli/commands/user.rb +28 -0
- data/lib/cli/commands/vms.rb +53 -0
- data/lib/cli/config.rb +154 -0
- data/lib/cli/core_ext.rb +145 -0
- data/lib/cli/dependency_helper.rb +62 -0
- data/lib/cli/deployment_helper.rb +263 -0
- data/lib/cli/deployment_manifest_compiler.rb +28 -0
- data/lib/cli/director.rb +633 -0
- data/lib/cli/director_task.rb +64 -0
- data/lib/cli/errors.rb +48 -0
- data/lib/cli/event_log_renderer.rb +351 -0
- data/lib/cli/job_builder.rb +226 -0
- data/lib/cli/package_builder.rb +254 -0
- data/lib/cli/packaging_helper.rb +248 -0
- data/lib/cli/release.rb +176 -0
- data/lib/cli/release_builder.rb +215 -0
- data/lib/cli/release_compiler.rb +178 -0
- data/lib/cli/release_tarball.rb +272 -0
- data/lib/cli/runner.rb +771 -0
- data/lib/cli/stemcell.rb +83 -0
- data/lib/cli/task_log_renderer.rb +40 -0
- data/lib/cli/templates/help_message.erb +75 -0
- data/lib/cli/validation.rb +42 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/version_calc.rb +48 -0
- data/lib/cli/versions_index.rb +126 -0
- data/lib/cli/yaml_helper.rb +62 -0
- data/spec/assets/biff/bad_gateway_config.yml +28 -0
- data/spec/assets/biff/good_simple_config.yml +63 -0
- data/spec/assets/biff/good_simple_golden_config.yml +63 -0
- data/spec/assets/biff/good_simple_template.erb +69 -0
- data/spec/assets/biff/multiple_subnets_config.yml +40 -0
- data/spec/assets/biff/network_only_template.erb +34 -0
- data/spec/assets/biff/no_cc_config.yml +27 -0
- data/spec/assets/biff/no_range_config.yml +27 -0
- data/spec/assets/biff/no_subnet_config.yml +16 -0
- data/spec/assets/biff/ok_network_config.yml +30 -0
- data/spec/assets/biff/properties_template.erb +6 -0
- data/spec/assets/deployment.MF +0 -0
- data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
- data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
- data/spec/assets/release/jobs/cacher.tgz +0 -0
- data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
- data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
- data/spec/assets/release/jobs/cacher/job.MF +6 -0
- data/spec/assets/release/jobs/cacher/monit +1 -0
- data/spec/assets/release/jobs/cleaner.tgz +0 -0
- data/spec/assets/release/jobs/cleaner/job.MF +4 -0
- data/spec/assets/release/jobs/cleaner/monit +1 -0
- data/spec/assets/release/jobs/sweeper.tgz +0 -0
- data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
- data/spec/assets/release/jobs/sweeper/job.MF +5 -0
- data/spec/assets/release/jobs/sweeper/monit +1 -0
- data/spec/assets/release/packages/mutator.tar.gz +0 -0
- data/spec/assets/release/packages/stuff.tgz +0 -0
- data/spec/assets/release/release.MF +17 -0
- data/spec/assets/release_invalid_checksum.tgz +0 -0
- data/spec/assets/release_invalid_jobs.tgz +0 -0
- data/spec/assets/release_no_name.tgz +0 -0
- data/spec/assets/release_no_version.tgz +0 -0
- data/spec/assets/stemcell/image +1 -0
- data/spec/assets/stemcell/stemcell.MF +6 -0
- data/spec/assets/stemcell_invalid_mf.tgz +0 -0
- data/spec/assets/stemcell_no_image.tgz +0 -0
- data/spec/assets/valid_release.tgz +0 -0
- data/spec/assets/valid_stemcell.tgz +0 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/base_command_spec.rb +66 -0
- data/spec/unit/biff_spec.rb +135 -0
- data/spec/unit/cache_spec.rb +36 -0
- data/spec/unit/cli_commands_spec.rb +481 -0
- data/spec/unit/config_spec.rb +139 -0
- data/spec/unit/core_ext_spec.rb +77 -0
- data/spec/unit/dependency_helper_spec.rb +52 -0
- data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
- data/spec/unit/director_spec.rb +511 -0
- data/spec/unit/director_task_spec.rb +48 -0
- data/spec/unit/event_log_renderer_spec.rb +171 -0
- data/spec/unit/hash_changeset_spec.rb +73 -0
- data/spec/unit/job_builder_spec.rb +454 -0
- data/spec/unit/package_builder_spec.rb +567 -0
- data/spec/unit/release_builder_spec.rb +65 -0
- data/spec/unit/release_spec.rb +66 -0
- data/spec/unit/release_tarball_spec.rb +33 -0
- data/spec/unit/runner_spec.rb +140 -0
- data/spec/unit/ssh_spec.rb +78 -0
- data/spec/unit/stemcell_spec.rb +17 -0
- data/spec/unit/version_calc_spec.rb +27 -0
- data/spec/unit/versions_index_spec.rb +132 -0
- metadata +338 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Bosh::Cli::Config do
|
|
6
|
+
before :each do
|
|
7
|
+
@config = File.join(Dir.mktmpdir, "bosh_config")
|
|
8
|
+
@cache_dir = Dir.mktmpdir
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_config(object)
|
|
12
|
+
File.open(@config, "w") do |f|
|
|
13
|
+
f.write(YAML.dump(object))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_config
|
|
18
|
+
Bosh::Cli::Config.new(@config)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def logged_in?(cfg)
|
|
22
|
+
cfg.username && cfg.password
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should convert old deployment configs to the new config " +
|
|
26
|
+
"when set_deployment is called" do
|
|
27
|
+
add_config("target" => "localhost:8080", "deployment" => "test")
|
|
28
|
+
|
|
29
|
+
cfg = create_config
|
|
30
|
+
yaml_file = load_yaml_file(@config, nil)
|
|
31
|
+
yaml_file["deployment"].should == "test"
|
|
32
|
+
cfg.set_deployment("test2")
|
|
33
|
+
cfg.save
|
|
34
|
+
yaml_file = load_yaml_file(@config, nil)
|
|
35
|
+
yaml_file["deployment"].has_key?("localhost:8080").should be_true
|
|
36
|
+
yaml_file["deployment"]["localhost:8080"].should == "test2"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should convert old deployment configs to the new config " +
|
|
40
|
+
"when deployment is called" do
|
|
41
|
+
add_config("target" => "localhost:8080", "deployment" => "test")
|
|
42
|
+
|
|
43
|
+
cfg = create_config
|
|
44
|
+
yaml_file = load_yaml_file(@config, nil)
|
|
45
|
+
yaml_file["deployment"].should == "test"
|
|
46
|
+
cfg.deployment.should == "test"
|
|
47
|
+
yaml_file = load_yaml_file(@config, nil)
|
|
48
|
+
yaml_file["deployment"].has_key?("localhost:8080").should be_true
|
|
49
|
+
yaml_file["deployment"]["localhost:8080"].should == "test"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should save a deployment for each target" do
|
|
53
|
+
add_config({})
|
|
54
|
+
cfg = create_config
|
|
55
|
+
cfg.target = "localhost:1"
|
|
56
|
+
cfg.set_deployment("/path/to/deploy/1")
|
|
57
|
+
cfg.save
|
|
58
|
+
cfg.target = "localhost:2"
|
|
59
|
+
cfg.set_deployment("/path/to/deploy/2")
|
|
60
|
+
cfg.save
|
|
61
|
+
|
|
62
|
+
# Test that the file is written correctly.
|
|
63
|
+
yaml_file = load_yaml_file(@config, nil)
|
|
64
|
+
yaml_file["deployment"].has_key?("localhost:1").should be_true
|
|
65
|
+
yaml_file["deployment"].has_key?("localhost:2").should be_true
|
|
66
|
+
yaml_file["deployment"]["localhost:1"].should == "/path/to/deploy/1"
|
|
67
|
+
yaml_file["deployment"]["localhost:2"].should == "/path/to/deploy/2"
|
|
68
|
+
|
|
69
|
+
# Test that switching targets gives you the new deployment.
|
|
70
|
+
cfg.deployment.should == "/path/to/deploy/2"
|
|
71
|
+
cfg.target = "localhost:1"
|
|
72
|
+
cfg.deployment.should == "/path/to/deploy/1"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "returns nil when the deployments key exists but has no value" do
|
|
76
|
+
add_config("target" => "localhost:8080", "deployment" => nil)
|
|
77
|
+
|
|
78
|
+
cfg = create_config
|
|
79
|
+
yaml_file = load_yaml_file(@config, nil)
|
|
80
|
+
yaml_file["deployment"].should == nil
|
|
81
|
+
cfg.deployment.should == nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should throw MissingTarget when getting deployment without target set" do
|
|
85
|
+
add_config({})
|
|
86
|
+
cfg = create_config
|
|
87
|
+
expect { cfg.set_deployment("/path/to/deploy/1") }.
|
|
88
|
+
to raise_error(Bosh::Cli::MissingTarget)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "whines on missing config file" do
|
|
92
|
+
lambda {
|
|
93
|
+
File.should_receive(:open).with(@config, "w").and_raise(Errno::EACCES)
|
|
94
|
+
create_config
|
|
95
|
+
}.should raise_error(Bosh::Cli::ConfigError)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
it "effectively ignores config file if it is malformed" do
|
|
100
|
+
add_config([1, 2, 3])
|
|
101
|
+
cfg = create_config
|
|
102
|
+
|
|
103
|
+
cfg.target.should == nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "fetches auth information from the config file" do
|
|
107
|
+
config = {
|
|
108
|
+
"target" => "localhost:8080",
|
|
109
|
+
"deployment" => "test",
|
|
110
|
+
"auth" => {
|
|
111
|
+
"localhost:8080" => { "username" => "a", "password" => "b" },
|
|
112
|
+
"localhost:8081" => { "username" => "c", "password" => "d" }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
add_config(config)
|
|
117
|
+
cfg = create_config
|
|
118
|
+
|
|
119
|
+
logged_in?(cfg).should be_true
|
|
120
|
+
cfg.username.should == "a"
|
|
121
|
+
cfg.password.should == "b"
|
|
122
|
+
|
|
123
|
+
config["target"] = "localhost:8081"
|
|
124
|
+
add_config(config)
|
|
125
|
+
|
|
126
|
+
cfg = create_config
|
|
127
|
+
logged_in?(cfg).should be_true
|
|
128
|
+
cfg.username.should == "c"
|
|
129
|
+
cfg.password.should == "d"
|
|
130
|
+
|
|
131
|
+
config["target"] = "localhost:8082"
|
|
132
|
+
add_config(config)
|
|
133
|
+
cfg = create_config
|
|
134
|
+
logged_in?(cfg).should be_false
|
|
135
|
+
cfg.username.should be_nil
|
|
136
|
+
cfg.password.should be_nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe String do
|
|
6
|
+
|
|
7
|
+
it "can tell valid bosh identifiers from invalid" do
|
|
8
|
+
%w(ruby ruby-1.8.7 mysql-2.3.5-alpha Apache_2.3).each do |id|
|
|
9
|
+
id.bosh_valid_id?.should be_true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
["ruby 1.8", "ruby-1.8@b29", "#!@", "db/2", "ruby(1.8)"].each do |id|
|
|
13
|
+
id.bosh_valid_id?.should be_false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "can tell blank string from non-blank" do
|
|
18
|
+
[" ", "\t\t", "\n", ""].each do |string|
|
|
19
|
+
string.should be_blank
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
["a", " a", "a ", " a ", "___", "z\tb"].each do |string|
|
|
23
|
+
string.should_not be_blank
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "has colorization helpers" do
|
|
28
|
+
Bosh::Cli::Config.colorize = false
|
|
29
|
+
"string".red.should == "string"
|
|
30
|
+
"string".green.should == "string"
|
|
31
|
+
"string".colorize("a").should == "string"
|
|
32
|
+
"string".colorize(:green).should == "string"
|
|
33
|
+
|
|
34
|
+
Bosh::Cli::Config.colorize = true
|
|
35
|
+
"string".red.should == "\e[0m\e[31mstring\e[0m"
|
|
36
|
+
"string".green.should == "\e[0m\e[32mstring\e[0m"
|
|
37
|
+
"string".colorize("a").should == "string"
|
|
38
|
+
"string".colorize(:green).should == "\e[0m\e[32mstring\e[0m"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe Object do
|
|
43
|
+
|
|
44
|
+
it "has output helpers" do
|
|
45
|
+
s = StringIO.new
|
|
46
|
+
Bosh::Cli::Config.output = s
|
|
47
|
+
say("yea")
|
|
48
|
+
say("yea")
|
|
49
|
+
s.rewind
|
|
50
|
+
s.read.should == "yea\nyea\n"
|
|
51
|
+
|
|
52
|
+
s.rewind
|
|
53
|
+
header("test")
|
|
54
|
+
s.rewind
|
|
55
|
+
s.read.should == "\ntest\n----\n"
|
|
56
|
+
|
|
57
|
+
s.rewind
|
|
58
|
+
header("test", "a")
|
|
59
|
+
s.rewind
|
|
60
|
+
s.read.should == "\ntest\naaaa\n"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "raises a special exception to signal a premature exit" do
|
|
64
|
+
lambda {
|
|
65
|
+
err("Done")
|
|
66
|
+
}.should raise_error(Bosh::Cli::CliExit, "Done")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "can tell if object is blank" do
|
|
70
|
+
o = Object.new
|
|
71
|
+
o.stub!(:to_s).and_return(" ")
|
|
72
|
+
o.should be_blank
|
|
73
|
+
o.stub!(:to_s).and_return("Object 1")
|
|
74
|
+
o.should_not be_blank
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Bosh::Cli::DependencyHelper do
|
|
6
|
+
|
|
7
|
+
def sorter
|
|
8
|
+
object = Object.new
|
|
9
|
+
class << object
|
|
10
|
+
include Bosh::Cli::DependencyHelper
|
|
11
|
+
end
|
|
12
|
+
object
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def tsort_packages(*args)
|
|
16
|
+
sorter.tsort_packages(*args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def partial_order_sort(*args)
|
|
20
|
+
sorter.partial_order_sort(*args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "resolves sorts simple dependencies" do
|
|
24
|
+
tsort_packages("A" => ["B"], "B" => ["C"], "C" => []).
|
|
25
|
+
should == ["C", "B", "A"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "whines on missing dependencies" do
|
|
29
|
+
lambda {
|
|
30
|
+
tsort_packages("A" => ["B"], "C" => ["D"])
|
|
31
|
+
}.should raise_error Bosh::Cli::MissingDependency,
|
|
32
|
+
"Package 'A' depends on missing package 'B'"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "whines on circular dependencies" do
|
|
36
|
+
lambda {
|
|
37
|
+
tsort_packages("foo" => ["bar"], "bar" => ["baz"], "baz" => ["foo"])
|
|
38
|
+
}.should raise_error(Bosh::Cli::CircularDependency,
|
|
39
|
+
"Cannot resolve dependencies for 'bar': " +
|
|
40
|
+
"circular dependency with 'foo'")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "can resolve nested dependencies" do
|
|
44
|
+
sorted = tsort_packages("A" => ["B", "C"], "B" => ["C", "D"],
|
|
45
|
+
"C" => ["D"], "D" => [], "E" => [])
|
|
46
|
+
sorted.index("B").should <= sorted.index("A")
|
|
47
|
+
sorted.index("C").should <= sorted.index("A")
|
|
48
|
+
sorted.index("D").should <= sorted.index("B")
|
|
49
|
+
sorted.index("D").should <= sorted.index("C")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Bosh::Cli::DeploymentManifestCompiler do
|
|
6
|
+
|
|
7
|
+
def make_compiler(manifest, properties = {})
|
|
8
|
+
compiler = Bosh::Cli::DeploymentManifestCompiler.new(manifest)
|
|
9
|
+
compiler.properties = properties
|
|
10
|
+
compiler
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "substitutes properties in a raw manifest" do
|
|
14
|
+
raw_manifest = <<-MANIFEST.gsub(/^\s*/, "")
|
|
15
|
+
---
|
|
16
|
+
name: mycloud
|
|
17
|
+
properties:
|
|
18
|
+
dea:
|
|
19
|
+
max_memory: <%= property("dea.max_memory") %>
|
|
20
|
+
MANIFEST
|
|
21
|
+
|
|
22
|
+
compiler = make_compiler(raw_manifest, { "dea.max_memory" => 8192 })
|
|
23
|
+
|
|
24
|
+
compiler.result.should == <<-MANIFEST.gsub(/^\s*/, "")
|
|
25
|
+
---
|
|
26
|
+
name: mycloud
|
|
27
|
+
properties:
|
|
28
|
+
dea:
|
|
29
|
+
max_memory: 8192
|
|
30
|
+
MANIFEST
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "whines on missing deployment properties" do
|
|
34
|
+
raw_manifest = <<-MANIFEST.gsub(/^\s*/, "")
|
|
35
|
+
---
|
|
36
|
+
name: mycloud
|
|
37
|
+
properties:
|
|
38
|
+
dea:
|
|
39
|
+
max_memory: <%= property("missing.property") %>
|
|
40
|
+
MANIFEST
|
|
41
|
+
|
|
42
|
+
compiler = make_compiler(raw_manifest, { "dea.max_memory" => 8192 })
|
|
43
|
+
error_msg = "Cannot resolve deployment property `missing.property'"
|
|
44
|
+
|
|
45
|
+
lambda {
|
|
46
|
+
compiler.result
|
|
47
|
+
}.should raise_error(Bosh::Cli::UndefinedProperty, error_msg)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "whines if manifest has syntax error (from ERB's point of view)" do
|
|
51
|
+
raw_manifest = <<-MANIFEST.gsub(/^\s*/, "")
|
|
52
|
+
properties: <%=
|
|
53
|
+
dea:
|
|
54
|
+
max_memory: <%= property("missing.property") %>
|
|
55
|
+
MANIFEST
|
|
56
|
+
|
|
57
|
+
compiler = make_compiler(raw_manifest, { "dea.max_memory" => 8192 })
|
|
58
|
+
|
|
59
|
+
lambda {
|
|
60
|
+
compiler.result
|
|
61
|
+
}.should raise_error(Bosh::Cli::MalformedManifest)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Bosh::Cli::Director do
|
|
6
|
+
|
|
7
|
+
DUMMY_TARGET = "http://target"
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
@director = Bosh::Cli::Director.new(DUMMY_TARGET, "user", "pass")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "fetching status" do
|
|
14
|
+
it "tells if user is authenticated" do
|
|
15
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
16
|
+
and_return([200, JSON.generate("user" => "adam")])
|
|
17
|
+
@director.authenticated?.should == true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "tells if user not authenticated" do
|
|
21
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
22
|
+
and_return([403, "Forbidden"])
|
|
23
|
+
@director.authenticated?.should == false
|
|
24
|
+
|
|
25
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
26
|
+
and_return([500, "Error"])
|
|
27
|
+
@director.authenticated?.should == false
|
|
28
|
+
|
|
29
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
30
|
+
and_return([404, "Not Found"])
|
|
31
|
+
@director.authenticated?.should == false
|
|
32
|
+
|
|
33
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
34
|
+
and_return([200, JSON.generate("user" => nil, "version" => 1)])
|
|
35
|
+
@director.authenticated?.should == false
|
|
36
|
+
|
|
37
|
+
# Backward compatibility
|
|
38
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
39
|
+
and_return([200, JSON.generate("status" => "ZB")])
|
|
40
|
+
@director.authenticated?.should == true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "interface REST API" do
|
|
45
|
+
it "has helper methods for HTTP verbs which delegate to generic request" do
|
|
46
|
+
[:get, :put, :post, :delete].each do |verb|
|
|
47
|
+
@director.should_receive(:request).with(verb, :arg1, :arg2)
|
|
48
|
+
@director.send(verb, :arg1, :arg2)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "API calls" do
|
|
54
|
+
it "creates user" do
|
|
55
|
+
@director.should_receive(:post).
|
|
56
|
+
with("/users", "application/json",
|
|
57
|
+
JSON.generate("username" => "joe", "password" => "pass")).
|
|
58
|
+
and_return(true)
|
|
59
|
+
@director.create_user("joe", "pass")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "uploads stemcell" do
|
|
63
|
+
@director.should_receive(:upload_and_track).
|
|
64
|
+
with("/stemcells", "application/x-compressed",
|
|
65
|
+
"/path", :log_type=>"event").and_return(true)
|
|
66
|
+
@director.upload_stemcell("/path")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "lists stemcells" do
|
|
70
|
+
@director.should_receive(:get).with("/stemcells", "application/json").
|
|
71
|
+
and_return([200, JSON.generate([]), {}])
|
|
72
|
+
@director.list_stemcells
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "lists releases" do
|
|
76
|
+
@director.should_receive(:get).with("/releases", "application/json").
|
|
77
|
+
and_return([200, JSON.generate([]), {}])
|
|
78
|
+
@director.list_releases
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "lists deployments" do
|
|
82
|
+
@director.should_receive(:get).with("/deployments", "application/json").
|
|
83
|
+
and_return([200, JSON.generate([]), {}])
|
|
84
|
+
@director.list_deployments
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "lists currently running tasks (director version < 0.3.5)" do
|
|
88
|
+
@director.should_receive(:get).with("/info", "application/json").
|
|
89
|
+
and_return([200, JSON.generate({ :version => "0.3.2"})])
|
|
90
|
+
@director.should_receive(:get).
|
|
91
|
+
with("/tasks?state=processing", "application/json").
|
|
92
|
+
and_return([200, JSON.generate([]), {}])
|
|
93
|
+
@director.list_running_tasks
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "lists currently running tasks (director version >= 0.3.5)" do
|
|
97
|
+
@director.should_receive(:get).
|
|
98
|
+
with("/info", "application/json").
|
|
99
|
+
and_return([200, JSON.generate({ :version => "0.3.5"})])
|
|
100
|
+
@director.should_receive(:get).
|
|
101
|
+
with("/tasks?state=processing,cancelling,queued", "application/json").
|
|
102
|
+
and_return([200, JSON.generate([]), {}])
|
|
103
|
+
@director.list_running_tasks
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "lists recent tasks" do
|
|
107
|
+
@director.should_receive(:get).
|
|
108
|
+
with("/tasks?limit=30", "application/json").
|
|
109
|
+
and_return([200, JSON.generate([]), {}])
|
|
110
|
+
@director.list_recent_tasks
|
|
111
|
+
|
|
112
|
+
@director.should_receive(:get).
|
|
113
|
+
with("/tasks?limit=100", "application/json").
|
|
114
|
+
and_return([200, JSON.generate([]), {}])
|
|
115
|
+
@director.list_recent_tasks(100000)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "uploads release" do
|
|
119
|
+
@director.should_receive(:upload_and_track).
|
|
120
|
+
with("/releases", "application/x-compressed",
|
|
121
|
+
"/path", :log_type => "event").
|
|
122
|
+
and_return(true)
|
|
123
|
+
@director.upload_release("/path")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "gets release info" do
|
|
127
|
+
@director.should_receive(:get).
|
|
128
|
+
with("/releases/foo", "application/json").
|
|
129
|
+
and_return([200, JSON.generate([]), { }])
|
|
130
|
+
@director.get_release("foo")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "gets deployment info" do
|
|
134
|
+
@director.should_receive(:get).
|
|
135
|
+
with("/deployments/foo", "application/json").
|
|
136
|
+
and_return([200, JSON.generate([]), { }])
|
|
137
|
+
@director.get_deployment("foo")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "deletes stemcell" do
|
|
141
|
+
@director.should_receive(:request_and_track).
|
|
142
|
+
with(:delete, "/stemcells/ubuntu/123",
|
|
143
|
+
nil, nil, :log_type => "event").
|
|
144
|
+
and_return(true)
|
|
145
|
+
@director.delete_stemcell("ubuntu", "123")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "deletes deployment" do
|
|
149
|
+
@director.should_receive(:request_and_track).
|
|
150
|
+
with(:delete, "/deployments/foo",
|
|
151
|
+
nil, nil, :log_type => "event").
|
|
152
|
+
and_return(true)
|
|
153
|
+
@director.delete_deployment("foo")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "deletes release (non-force)" do
|
|
157
|
+
@director.should_receive(:request_and_track).
|
|
158
|
+
with(:delete, "/releases/za",
|
|
159
|
+
nil, nil, :log_type => "event").
|
|
160
|
+
and_return(true)
|
|
161
|
+
@director.delete_release("za")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "deletes release (force)" do
|
|
165
|
+
@director.should_receive(:request_and_track).
|
|
166
|
+
with(:delete, "/releases/zb?force=true",
|
|
167
|
+
nil, nil, :log_type => "event").
|
|
168
|
+
and_return(true)
|
|
169
|
+
@director.delete_release("zb", :force => true)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "deploys" do
|
|
173
|
+
@director.should_receive(:request_and_track).
|
|
174
|
+
with(:post, "/deployments", "text/yaml",
|
|
175
|
+
"manifest", :log_type => "event").
|
|
176
|
+
and_return(true)
|
|
177
|
+
@director.deploy("manifest")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "changes job state" do
|
|
181
|
+
@director.should_receive(:request_and_track).
|
|
182
|
+
with(:put, "/deployments/foo/jobs/dea?state=stopped",
|
|
183
|
+
"text/yaml", "manifest", :log_type => "event").
|
|
184
|
+
and_return(true)
|
|
185
|
+
@director.change_job_state("foo", "manifest", "dea", nil, "stopped")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "changes job instance state" do
|
|
189
|
+
@director.should_receive(:request_and_track).
|
|
190
|
+
with(:put, "/deployments/foo/jobs/dea/0?state=detached",
|
|
191
|
+
"text/yaml", "manifest", :log_type => "event").
|
|
192
|
+
and_return(true)
|
|
193
|
+
@director.change_job_state("foo", "manifest", "dea", 0, "detached")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "gets task state" do
|
|
197
|
+
@director.should_receive(:get).
|
|
198
|
+
with("/tasks/232").
|
|
199
|
+
and_return([200, JSON.generate({ "state" => "done" })])
|
|
200
|
+
@director.get_task_state(232).should == "done"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "whines on missing task" do
|
|
204
|
+
@director.should_receive(:get).
|
|
205
|
+
with("/tasks/232").
|
|
206
|
+
and_return([404, "Not Found"])
|
|
207
|
+
lambda {
|
|
208
|
+
@director.get_task_state(232).should
|
|
209
|
+
}.should raise_error(Bosh::Cli::MissingTask)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "gets task output" do
|
|
213
|
+
@director.should_receive(:get).
|
|
214
|
+
with("/tasks/232/output", nil,
|
|
215
|
+
nil, { "Range" => "bytes=42-" }).
|
|
216
|
+
and_return([206, "test", { :content_range => "bytes 42-56/100" }])
|
|
217
|
+
@director.get_task_output(232, 42).should == ["test", 57]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "doesn't set task output new offset if it wasn't a partial response" do
|
|
221
|
+
@director.should_receive(:get).
|
|
222
|
+
with("/tasks/232/output", nil, nil,
|
|
223
|
+
{ "Range" => "bytes=42-" }).
|
|
224
|
+
and_return([200, "test"])
|
|
225
|
+
@director.get_task_output(232, 42).should == ["test", nil]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "know how to find time difference with director" do
|
|
229
|
+
now = Time.now
|
|
230
|
+
server_time = now - 100
|
|
231
|
+
Time.stub!(:now).and_return(now)
|
|
232
|
+
|
|
233
|
+
@director.should_receive(:get).with("/info").
|
|
234
|
+
and_return([200, JSON.generate("version" => 1),
|
|
235
|
+
{ :date => server_time.rfc822 }])
|
|
236
|
+
@director.get_time_difference.to_i.should == 100
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
describe "checking status" do
|
|
242
|
+
it "considers target valid if it responds with 401 (for compatibility)" do
|
|
243
|
+
@director.stub(:get).
|
|
244
|
+
with("/info", "application/json").
|
|
245
|
+
and_return([401, "Not authorized"])
|
|
246
|
+
@director.exists?.should be_true
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "considers target valid if it responds with 200" do
|
|
250
|
+
@director.stub(:get).
|
|
251
|
+
with("/info", "application/json").
|
|
252
|
+
and_return([200, JSON.generate("name" => "Director is your friend")])
|
|
253
|
+
@director.exists?.should be_true
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
describe "tracking request" do
|
|
258
|
+
it "starts polling task if request responded with a redirect to task URL" do
|
|
259
|
+
@director.should_receive(:request).
|
|
260
|
+
with(:get, "/stuff", "text/plain", "abc").
|
|
261
|
+
and_return([302, "body", { :location => "/tasks/502" }])
|
|
262
|
+
@director.should_receive(:poll_task).
|
|
263
|
+
with("502", :arg1 => 1, :arg2 => 2).
|
|
264
|
+
and_return("polling result")
|
|
265
|
+
@director.request_and_track(:get, "/stuff", "text/plain",
|
|
266
|
+
"abc", :arg1 => 1, :arg2 => 2).
|
|
267
|
+
should == ["polling result", "502"]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "considers all reponses but 302 a failure" do
|
|
271
|
+
[200, 404, 403].each do |code|
|
|
272
|
+
@director.should_receive(:request).
|
|
273
|
+
with(:get, "/stuff", "text/plain", "abc").
|
|
274
|
+
and_return([code, "body", { }])
|
|
275
|
+
@director.request_and_track(:get, "/stuff", "text/plain",
|
|
276
|
+
"abc", :arg1 => 1, :arg2 => 2).
|
|
277
|
+
should == [:failed, nil]
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "reports task as non trackable if its URL is unfamiliar" do
|
|
282
|
+
@director.should_receive(:request).
|
|
283
|
+
with(:get, "/stuff", "text/plain", "abc").
|
|
284
|
+
and_return([302, "body", { :location => "/track-task/502" }])
|
|
285
|
+
@director.request_and_track(:get, "/stuff", "text/plain",
|
|
286
|
+
"abc", :arg1 => 1, :arg2 => 2).
|
|
287
|
+
should == [:non_trackable, nil]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "suppports uploading with progress bar" do
|
|
291
|
+
file = spec_asset("valid_release.tgz")
|
|
292
|
+
f = Bosh::Cli::FileWithProgressBar.open(file, "r")
|
|
293
|
+
|
|
294
|
+
Bosh::Cli::FileWithProgressBar.stub!(:open).with(file, "r").and_return(f)
|
|
295
|
+
@director.should_receive(:request_and_track).
|
|
296
|
+
with(:post, "/stuff", "application/x-compressed", f, { })
|
|
297
|
+
@director.upload_and_track("/stuff", "application/x-compressed", file)
|
|
298
|
+
f.progress_bar.finished?.should be_true
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
describe "performing HTTP requests" do
|
|
303
|
+
it "delegates to HTTPClient" do
|
|
304
|
+
headers = { "Content-Type" => "app/zb", "a" => "b", "c" => "d"}
|
|
305
|
+
user = "user"
|
|
306
|
+
password = "pass"
|
|
307
|
+
auth = "Basic " + Base64.encode64("#{user}:#{password}").strip
|
|
308
|
+
|
|
309
|
+
client = mock("httpclient")
|
|
310
|
+
client.should_receive(:send_timeout=).
|
|
311
|
+
with(Bosh::Cli::Director::API_TIMEOUT)
|
|
312
|
+
client.should_receive(:receive_timeout=).
|
|
313
|
+
with(Bosh::Cli::Director::API_TIMEOUT)
|
|
314
|
+
client.should_receive(:connect_timeout=).
|
|
315
|
+
with(Bosh::Cli::Director::CONNECT_TIMEOUT)
|
|
316
|
+
HTTPClient.stub!(:new).and_return(client)
|
|
317
|
+
|
|
318
|
+
client.should_receive(:request).
|
|
319
|
+
with(:get, "http://target/stuff", :body => "payload",
|
|
320
|
+
:header => headers.merge("Authorization" => auth))
|
|
321
|
+
@director.send(:perform_http_request, :get,
|
|
322
|
+
"http://target/stuff", "payload", headers)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
describe "talking to REST API" do
|
|
327
|
+
it "performs HTTP request" do
|
|
328
|
+
mock_response = mock("response", :code => 200,
|
|
329
|
+
:body => "test", :headers => {})
|
|
330
|
+
|
|
331
|
+
@director.should_receive(:perform_http_request).
|
|
332
|
+
with(:get, "http://target/stuff", "payload", "h1" => "a",
|
|
333
|
+
"h2" => "b", "Content-Type" => "app/zb").
|
|
334
|
+
and_return(mock_response)
|
|
335
|
+
|
|
336
|
+
@director.request(:get, "/stuff", "app/zb", "payload",
|
|
337
|
+
{ "h1" => "a", "h2" => "b"}).
|
|
338
|
+
should == [200, "test", {}]
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
it "nicely wraps director error response" do
|
|
342
|
+
[400, 403, 500].each do |code|
|
|
343
|
+
lambda {
|
|
344
|
+
# Familiar JSON
|
|
345
|
+
body = JSON.generate("code" => "40422",
|
|
346
|
+
"description" => "Weird stuff happened")
|
|
347
|
+
|
|
348
|
+
mock_response = mock("response",
|
|
349
|
+
:code => code,
|
|
350
|
+
:body => body,
|
|
351
|
+
:headers => {})
|
|
352
|
+
|
|
353
|
+
@director.should_receive(:perform_http_request).
|
|
354
|
+
and_return(mock_response)
|
|
355
|
+
@director.request(:get, "/stuff", "application/octet-stream",
|
|
356
|
+
"payload", { :hdr1 => "a", :hdr2 => "b"})
|
|
357
|
+
}.should raise_error(Bosh::Cli::DirectorError,
|
|
358
|
+
"Director error 40422: Weird stuff happened")
|
|
359
|
+
|
|
360
|
+
lambda {
|
|
361
|
+
# Not JSON
|
|
362
|
+
mock_response = mock("response", :code => code,
|
|
363
|
+
:body => "error message goes here",
|
|
364
|
+
:headers => {})
|
|
365
|
+
@director.should_receive(:perform_http_request).
|
|
366
|
+
and_return(mock_response)
|
|
367
|
+
@director.request(:get, "/stuff", "application/octet-stream",
|
|
368
|
+
"payload", { :hdr1 => "a", :hdr2 => "b"})
|
|
369
|
+
}.should raise_error(Bosh::Cli::DirectorError,
|
|
370
|
+
"Director error (HTTP #{code}): " +
|
|
371
|
+
"error message goes here")
|
|
372
|
+
|
|
373
|
+
lambda {
|
|
374
|
+
# JSON but weird
|
|
375
|
+
mock_response = mock("response", :code => code,
|
|
376
|
+
:body => '{"c":"d","a":"b"}',
|
|
377
|
+
:headers => {})
|
|
378
|
+
@director.should_receive(:perform_http_request).
|
|
379
|
+
and_return(mock_response)
|
|
380
|
+
@director.request(:get, "/stuff", "application/octet-stream",
|
|
381
|
+
"payload", { :hdr1 => "a", :hdr2 => "b"})
|
|
382
|
+
}.should raise_error(Bosh::Cli::DirectorError,
|
|
383
|
+
"Director error (HTTP #{code}): " +
|
|
384
|
+
%Q[{"c":"d","a":"b"}])
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
it "wraps director access exceptions" do
|
|
389
|
+
[URI::Error, SocketError, Errno::ECONNREFUSED].each do |err|
|
|
390
|
+
@director.should_receive(:perform_http_request).
|
|
391
|
+
and_raise(err.new("err message"))
|
|
392
|
+
lambda {
|
|
393
|
+
@director.request(:get, "/stuff", "app/zb", "payload", { })
|
|
394
|
+
}.should raise_error(Bosh::Cli::DirectorInaccessible)
|
|
395
|
+
end
|
|
396
|
+
@director.should_receive(:perform_http_request).
|
|
397
|
+
and_raise(SystemCallError.new("err message"))
|
|
398
|
+
lambda {
|
|
399
|
+
@director.request(:get, "/stuff", "app/zb", "payload", { })
|
|
400
|
+
}.should raise_error Bosh::Cli::DirectorError
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
it "streams file" do
|
|
404
|
+
mock_response = mock("response", :code => 200,
|
|
405
|
+
:body => "test body", :headers => { })
|
|
406
|
+
@director.should_receive(:perform_http_request).
|
|
407
|
+
and_yield("test body").and_return(mock_response)
|
|
408
|
+
|
|
409
|
+
code, filename, headers = @director.request(:get,
|
|
410
|
+
"/files/foo", nil, nil,
|
|
411
|
+
{ }, { :file => true })
|
|
412
|
+
|
|
413
|
+
code.should == 200
|
|
414
|
+
File.read(filename).should == "test body"
|
|
415
|
+
headers.should == { }
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
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(1).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
|
+
end
|