vcloud-core 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
+ ## 0.12.0 (2014-10-03)
2
+
3
+ Features:
4
+
5
+ - Adds custom fields support to vApps. Thanks @geriBatai
6
+ - Adds IndependentDisk entity class, for creation and discovery of Independent Disks. Thanks @mikepea
7
+ - Adds methods to attach and detach IndependentDisk objects to/from an existing VM. Thanks @mikepea
8
+ - Add Vapp.get_by_child_vm_id method, to find a vApp by its child VM id. Thanks @mikepea
9
+
1
10
  ## 0.11.0 (2014-09-11)
2
11
 
3
- Changes:
12
+ Features:
4
13
 
5
14
  - As deprecated in 0.7.0, it is now impossible to specify a plaintext
6
15
  password in a FOG_RC file. Please use tokens via vcloud-login as per
@@ -10,7 +19,7 @@ Changes:
10
19
 
11
20
  API changes:
12
21
 
13
- - removes the temporary files used for transitioning vCloud Tools Tester to use the new API.
22
+ - Removes the temporary files used for transitioning vCloud Tools Tester to use the new API.
14
23
 
15
24
  ## 0.9.0 (2014-08-08)
16
25
 
data/CONTRIBUTING.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  We really welcome contributions.
4
4
 
5
+ ## Issues list
6
+
7
+ Issues we know about are [listed here](https://github.com/gds-operations/vcloud-core/issues). We have a [`newcomer-friendly label`](https://github.com/gds-operations/vcloud-core/labels/newcomer-friendly) for issues that we feel would be a good place to start, because they do not touch a lot of other vCloud Tools code and do not require an environment to work on. This label is just a suggestion.
8
+
5
9
  ## A quick guide on how to contribute
6
10
 
7
11
  1. Clone the repo:
data/README.md CHANGED
@@ -103,6 +103,28 @@ If you want to be sure you are pinning to 5.1, or use 5.5, you can set the API v
103
103
 
104
104
  `vcloud_director_api_version: 5.1`
105
105
 
106
+ ## Working with Independent Disks
107
+
108
+ vCloud Core now supports the management of Independent Disks -- block devices
109
+ stored and managed separately from the VMs they are attached to. We have
110
+ noticed that this bring some limitations/caveats into play that API users
111
+ should be aware of:
112
+
113
+ * It is not possible to move the VM from one Storage Profile to another with
114
+ Vm#update_storage_profile if an Independent Disk is attached. This appears to
115
+ be a limitation in vCloud Director itself. To work around this, detach the
116
+ disks before updating, and reattach afterwards.
117
+
118
+ * It is not possible to add additional *local* disks via Vm#add_extra_disks
119
+ when Independent Disks are attached to a VM. This appears to be a limitation with
120
+ Fog, as the vCD UI permits it. See https://github.com/fog/fog/issues/3179
121
+ for progress on this issue.
122
+
123
+ * Extreme care should be taken when detaching Independent Disks from a VM, as
124
+ vCloud Director will detach them without warning from running VMs, and hence
125
+ with no notification to the running OS. It is recommended to simply use them for
126
+ persistence across VM delete/recreate operations.
127
+
106
128
  ## Debugging
107
129
 
108
130
  `export EXCON_DEBUG=true` - this will print out the API requests and responses.
data/lib/vcloud/core.rb CHANGED
@@ -16,6 +16,7 @@ require 'vcloud/core/login_cli'
16
16
  require 'vcloud/core/vm'
17
17
  require 'vcloud/core/vapp'
18
18
  require 'vcloud/core/vapp_template'
19
+ require 'vcloud/core/independent_disk'
19
20
  require 'vcloud/core/org_vdc_network'
20
21
  require 'vcloud/core/query'
21
22
  require 'vcloud/core/query_cli'
@@ -8,6 +8,7 @@ module Vcloud
8
8
  module ContentTypes
9
9
  ORG = 'application/vnd.vmware.vcloud.org+xml'
10
10
  VDC = 'application/vnd.vmware.vcloud.vdc+xml'
11
+ VAPP = 'application/vnd.vmware.vcloud.vApp+xml'
11
12
  NETWORK = 'application/vnd.vmware.vcloud.network+xml'
12
13
  METADATA = 'application/vnd.vmware.vcloud.metadata.value+xml'
13
14
  end
@@ -16,7 +16,9 @@ module Vcloud
16
16
  :get_execute_query, :get_vapp_metadata, :power_off_vapp, :shutdown_vapp, :session,
17
17
  :post_instantiate_vapp_template, :put_memory, :put_cpu, :power_on_vapp, :put_vapp_metadata_value,
18
18
  :put_vm, :get_edge_gateway, :get_network_complete, :delete_network, :post_create_org_vdc_network,
19
- :post_configure_edge_gateway_services, :get_vdc, :post_undeploy_vapp
19
+ :post_configure_edge_gateway_services, :get_vdc, :post_undeploy_vapp,
20
+ :post_create_disk, :get_disk, :delete_disk, :post_attach_disk,
21
+ :get_vms_disk_attached_to, :post_detach_disk, :put_product_sections
20
22
 
21
23
  #########################
22
24
  # FogFacade Inner class to represent a logic free facade over our interactions with Fog
@@ -125,6 +127,37 @@ module Vcloud
125
127
  @vcloud.process_task(task)
126
128
  end
127
129
 
130
+ def get_disk(id)
131
+ @vcloud.get_disk(id).body
132
+ end
133
+
134
+ def delete_disk(id)
135
+ task = @vcloud.delete_disk(id).body
136
+ @vcloud.process_task(task)
137
+ end
138
+
139
+ def post_create_disk(vdc_id, disk_id, size_in_bytes, options = {})
140
+ # Fog method is incorrectly named 'post_upload_disk', and will be fixed
141
+ # in a future version to match our post_create_disk method name.
142
+ attrs = @vcloud.post_upload_disk(vdc_id, disk_id, size_in_bytes, options).body
143
+ @vcloud.process_task(attrs[:Tasks][:Task])
144
+ get_disk(extract_id(attrs))
145
+ end
146
+
147
+ def post_attach_disk(vm_id, disk_id, options = {})
148
+ task = @vcloud.post_attach_disk(vm_id, disk_id, options).body
149
+ @vcloud.process_task(task)
150
+ end
151
+
152
+ def post_detach_disk(vm_id, disk_id)
153
+ task = @vcloud.post_detach_disk(vm_id, disk_id).body
154
+ @vcloud.process_task(task)
155
+ end
156
+
157
+ def get_vms_disk_attached_to(disk_id)
158
+ @vcloud.get_vms_disk_attached_to(disk_id).body
159
+ end
160
+
128
161
  def post_create_org_vdc_network(vdc_id, name, options)
129
162
  Vcloud::Core.logger.debug("creating #{options[:fence_mode]} OrgVdcNetwork #{name} in vDC #{vdc_id}")
130
163
  attrs = @vcloud.post_create_org_vdc_network(vdc_id, name, options).body
@@ -170,6 +203,11 @@ module Vcloud
170
203
  @vcloud.get_edge_gateway(id).body
171
204
  end
172
205
 
206
+ def put_product_sections(id, items)
207
+ task = @vcloud.put_product_sections(id, items).body
208
+ @vcloud.process_task(task)
209
+ end
210
+
173
211
  private
174
212
  def extract_id(link)
175
213
  link[:href].split('/').last
@@ -0,0 +1,95 @@
1
+ module Vcloud
2
+ module Core
3
+ class IndependentDisk
4
+
5
+ class QueryExecutionError < RuntimeError; end
6
+ class DiskNotFoundException < RuntimeError; end
7
+ class MultipleDisksFoundException < RuntimeError; end
8
+ class DiskAlreadyExistsException < RuntimeError; end
9
+
10
+ attr_reader :id
11
+
12
+ def initialize(id)
13
+ unless id =~ /^[-0-9a-f]+$/
14
+ raise "IndependentDisk id : #{id} is not in correct format"
15
+ end
16
+ @id = id
17
+ end
18
+
19
+ def self.get_by_name_and_vdc_name(name, vdc_name)
20
+ q = Vcloud::Core::QueryRunner.new
21
+ query_results = q.run('disk', :filter => "name==#{name};vdcName==#{vdc_name}")
22
+ unless query_results
23
+ raise QueryExecutionError,
24
+ "Error finding IndependentDisk by name '#{name}' & vdc '#{vdc_name}'"
25
+ end
26
+ raise DiskNotFoundException,
27
+ "IndependentDisk '#{name}' not found in vDC '#{vdc_name}'" if query_results.size == 0
28
+ if query_results.size > 1
29
+ raise MultipleDisksFoundException,
30
+ "Multiple IndependentDisks matching '#{name}' found in vDC '#{vdc_name}. " +
31
+ "Create disks via IndependentDisk.new(disk_id) instead."
32
+ end
33
+ return self.new(query_results.first[:href].split('/').last)
34
+ end
35
+
36
+ def self.create(vdc, name, size)
37
+ vdc_name = vdc.name
38
+ begin
39
+ self.get_by_name_and_vdc_name(name, vdc_name)
40
+ rescue DiskNotFoundException
41
+ ok_to_create = true
42
+ end
43
+
44
+ unless ok_to_create
45
+ raise DiskAlreadyExistsException,
46
+ "Cannot create Independent Disk '#{name}' in vDC '#{vdc_name}' - a disk with " +
47
+ "that name is already present"
48
+ end
49
+
50
+ size_in_bytes = convert_size_to_bytes(size)
51
+ body = Vcloud::Core::Fog::ServiceInterface.new.post_create_disk(vdc.id, name, size_in_bytes)
52
+ return self.new(body[:href].split('/').last)
53
+ end
54
+
55
+ def vcloud_attributes
56
+ Vcloud::Core::Fog::ServiceInterface.new.get_disk(id)
57
+ end
58
+
59
+ def name
60
+ vcloud_attributes[:name]
61
+ end
62
+
63
+ def href
64
+ vcloud_attributes[:href]
65
+ end
66
+
67
+ def attached_vms
68
+ body = Vcloud::Core::Fog::ServiceInterface.new.get_vms_disk_attached_to(id)
69
+ vms = body.fetch(:VmReference)
70
+ vms.map do |vm|
71
+ id = vm.fetch(:href).split('/').last
72
+ parent_vapp = Vcloud::Core::Vapp.get_by_child_vm_id(id)
73
+ Vcloud::Core::Vm.new(id, parent_vapp)
74
+ end
75
+ end
76
+
77
+ def self.convert_size_to_bytes(size)
78
+ if size.to_s =~ /^(\d+)mb$/i
79
+ Integer($1) * (10**6)
80
+ elsif size.to_s =~ /^(\d+)gb$/i
81
+ Integer($1) * (10**9)
82
+ elsif size.to_s =~ /^(\d+)mib$/i
83
+ Integer($1) * (2**20)
84
+ elsif size.to_s =~ /^(\d+)gib$/i
85
+ Integer($1) * (2**30)
86
+ elsif size.to_s =~ /^(\d+)$/i
87
+ Integer($1)
88
+ else
89
+ raise ArgumentError, "Cannot convert size string '#{size}' into a number of bytes"
90
+ end
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -26,6 +26,18 @@ module Vcloud
26
26
  end
27
27
  end
28
28
 
29
+ def self.get_by_child_vm_id(vm_id)
30
+ raise ArgumentError, "Must supply a valid Vm id" unless vm_id =~ /^vm-[-0-9a-f]+$/
31
+ vm_body = Vcloud::Core::Fog::ServiceInterface.new.get_vapp(vm_id)
32
+ parent_vapp_link = vm_body.fetch(:Link).detect do |link|
33
+ link[:rel] == Fog::RELATION::PARENT && link[:type] == Fog::ContentTypes::VAPP
34
+ end
35
+ unless parent_vapp_link
36
+ raise RuntimeError, "Could not find parent vApp for VM '#{vm_id}'"
37
+ end
38
+ return self.new(parent_vapp_link.fetch(:href).split('/').last)
39
+ end
40
+
29
41
  def vcloud_attributes
30
42
  Vcloud::Core::Fog::ServiceInterface.new.get_vapp(id)
31
43
  end
@@ -76,6 +88,26 @@ module Vcloud
76
88
  self.new(attrs[:href].split('/').last) if attrs and attrs.key?(:href)
77
89
  end
78
90
 
91
+ def update_custom_fields(custom_fields)
92
+ return if custom_fields.nil?
93
+ fields = custom_fields.collect do |field|
94
+ user_configurable = field[:user_configurable] || true
95
+ type = field[:type] || 'string'
96
+ password = field[:password] || false
97
+
98
+ {
99
+ :id => field[:name],
100
+ :value => field[:value],
101
+ :user_configurable => user_configurable,
102
+ :type => type,
103
+ :password => password
104
+ }
105
+ end
106
+
107
+ Vcloud::Core.logger.debug("adding custom fields #{fields.inspect} to vapp #{@id}")
108
+ Vcloud::Core::Fog::ServiceInterface.new.put_product_sections(@id, fields)
109
+ end
110
+
79
111
  def power_on
80
112
  raise "Cannot power on a missing vApp." unless id
81
113
  return true if running?
@@ -1,5 +1,5 @@
1
1
  module Vcloud
2
2
  module Core
3
- VERSION = '0.11.0'
3
+ VERSION = '0.12.0'
4
4
  end
5
5
  end
@@ -69,6 +69,20 @@ module Vcloud
69
69
  end
70
70
  end
71
71
 
72
+ def attach_independent_disks(disk_list)
73
+ disk_list = Array(disk_list) # ensure we have an array
74
+ disk_list.each do |disk|
75
+ Vcloud::Core::Fog::ServiceInterface.new.post_attach_disk(id, disk.id)
76
+ end
77
+ end
78
+
79
+ def detach_independent_disks(disk_list)
80
+ disk_list = Array(disk_list) # ensure we have an array
81
+ disk_list.each do |disk|
82
+ Vcloud::Core::Fog::ServiceInterface.new.post_detach_disk(id, disk.id)
83
+ end
84
+ end
85
+
72
86
  def add_extra_disks(extra_disks)
73
87
  vm = Vcloud::Core::Fog::ModelInterface.new.get_vm_by_href(href)
74
88
  if extra_disks
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vcloud::Core::IndependentDisk do
4
+
5
+ let(:uuid_matcher) { "[-0-9a-f]+" }
6
+
7
+ before(:all) do
8
+ config_file = File.join(File.dirname(__FILE__), "../vcloud_tools_testing_config.yaml")
9
+ required_user_params = [
10
+ "vdc_1_name",
11
+ ]
12
+
13
+ @test_params = Vcloud::Tools::Tester::TestSetup.new(config_file, required_user_params).test_params
14
+ @disk_name_prefix = "vcloud-core-independent-disk-tests"
15
+ quantity_of_test_case_disks = 1
16
+ @vdc_name = @test_params.vdc_1_name
17
+ @vdc = Vcloud::Core::Vdc.get_by_name(@vdc_name)
18
+ @test_disk_size = 12000000 # bytes
19
+ @test_case_disks = IntegrationHelper.create_test_case_independent_disks(
20
+ quantity_of_test_case_disks,
21
+ @vdc_name,
22
+ @test_disk_size,
23
+ @disk_name_prefix
24
+ )
25
+ @test_disk = @test_case_disks.first
26
+ end
27
+
28
+ subject(:fixture_disk) { @test_disk }
29
+
30
+ context "before the integration tests run" do
31
+
32
+ it "ensures we have a valid IndependentDisk fixture, for subsequent tests to run against" do
33
+ expect(fixture_disk).to be_instance_of(Vcloud::Core::IndependentDisk)
34
+ end
35
+
36
+ end
37
+
38
+ describe "#vcloud_attributes" do
39
+
40
+ it "has a :href element containing the expected Independent Disk id" do
41
+ expect(fixture_disk.vcloud_attributes[:href].split('/').last).to eq(fixture_disk.id)
42
+ end
43
+
44
+ end
45
+
46
+ describe "#id" do
47
+
48
+ it "returns the a valid Independent Disk id" do
49
+ expect(fixture_disk.id).to match(/^#{uuid_matcher}$/)
50
+ end
51
+
52
+ end
53
+
54
+ describe "#name" do
55
+
56
+ it "returns the name of the Independent Disk" do
57
+ expect(fixture_disk.name).to include(@disk_name_prefix)
58
+ end
59
+
60
+ end
61
+
62
+ describe "#get_by_name_and_vdc_name" do
63
+
64
+ it "can find our fixture Independent Disk by its name & vdcName" do
65
+ retrieved_disk = Vcloud::Core::IndependentDisk.get_by_name_and_vdc_name(
66
+ fixture_disk.name, @vdc_name)
67
+ expect(retrieved_disk.id).to eq(fixture_disk.id)
68
+ end
69
+
70
+ it "raises an error if it cannot find the named Independent Disk" do
71
+ bogus_disk_name = "bogus-disk-name-wefoiuhwef"
72
+ expect {
73
+ Vcloud::Core::IndependentDisk.get_by_name_and_vdc_name(
74
+ bogus_disk_name, @vdc_name)
75
+ }.to raise_error(RuntimeError,
76
+ "IndependentDisk '#{bogus_disk_name}' not found in vDC '#{@vdc_name}'"
77
+ )
78
+ end
79
+
80
+ end
81
+
82
+ describe "#create" do
83
+
84
+ let(:disk_name) { "#{@disk_name_prefix}-instantiate-test-disk" }
85
+
86
+ it "can create a Independent Disk" do
87
+ new_disk = Vcloud::Core::IndependentDisk.create(
88
+ @vdc,
89
+ disk_name,
90
+ 10000000,
91
+ )
92
+ @test_case_disks << new_disk
93
+ expect(new_disk.name).to eq(disk_name)
94
+ end
95
+
96
+ it "raises a DiskAlreadyExistsException if we try to create a disk with the same " +
97
+ "name in the same vDC" do
98
+ expect { Vcloud::Core::IndependentDisk.create(
99
+ @vdc,
100
+ disk_name,
101
+ 10000000)
102
+ }.to raise_error(Vcloud::Core::IndependentDisk::DiskAlreadyExistsException)
103
+ end
104
+
105
+ end
106
+
107
+ after(:all) do
108
+ IntegrationHelper.delete_independent_disks(@test_case_disks)
109
+ end
110
+
111
+
112
+ end
@@ -30,6 +30,12 @@ describe Vcloud::Core::Vm do
30
30
  @network_names,
31
31
  "vcloud-core-vm-tests"
32
32
  )
33
+ @test_case_independent_disk_list = IntegrationHelper.create_test_case_independent_disks(
34
+ 1,
35
+ @test_params.vdc_1_name,
36
+ "100MB",
37
+ "vcloud-core-vm-test-disk"
38
+ )
33
39
  @vapp = @test_case_vapps.first
34
40
  vapp_vms = @vapp.vms.map do |vm|
35
41
  vm_id = vm[:href].split('/').last
@@ -46,6 +52,7 @@ describe Vcloud::Core::Vm do
46
52
 
47
53
  end
48
54
 
55
+
49
56
  context "#update_memory_size_in_mb" do
50
57
 
51
58
  it "can increase the memory size by 512MB" do
@@ -231,26 +238,86 @@ describe Vcloud::Core::Vm do
231
238
 
232
239
  end
233
240
 
234
- context "#update_storage_profile" do
241
+ context "#attach_independent_disks" do
242
+ it "can attach our fixture disk" do
243
+ disk = @test_case_independent_disk_list.first
244
+ expect(disk.attached_vms).to be_empty
245
+ @vm.attach_independent_disks(@test_case_independent_disk_list)
246
+ expect(disk.attached_vms.first.id).to eq(@vm.id)
247
+ end
248
+ end
249
+
250
+ # NB: It is suspected that this behaviour is caused by the Fog Model, not
251
+ # vCloud Director itself. Issue raised on fog/fog to investigate/fix:
252
+ # https://github.com/fog/fog/issues/3179
253
+ context "local disks cannot be added whilst an independent disk is attached" do
254
+
255
+ it "raises an error if we now try to add an extra local disk" do
256
+ extra_disks = [ { size: '10240' } ]
257
+ expect { @vm.add_extra_disks(extra_disks) }.
258
+ to raise_error(
259
+ Fog::Compute::VcloudDirector::BadRequest,
260
+ "The attached disks on VM \"#{@vm.name}\" cannot be modified."
261
+ )
262
+ end
263
+
264
+ end
265
+
266
+ describe "cannot update the storage profile of a VM with an independent disk attached" do
267
+ context "#update_storage_profile" do
268
+
269
+ it "throws an error when trying to update the storage profile of a VM with an " +
270
+ "independent disk attached" do
271
+ available_storage_profiles = Vcloud::Core::QueryRunner.new.run(
272
+ 'orgVdcStorageProfile',
273
+ filter: "vdcName==#{@test_params.vdc_1_name}"
274
+ )
275
+ if available_storage_profiles.size == 1
276
+ pending("There is only one StorageProfile in vDC #{@test_params.vdc_1_name}: cannot test.")
277
+ end
278
+ expect{ @vm.update_storage_profile(@test_params.storage_profile) }.
279
+ to raise_error(Fog::Compute::VcloudDirector::TaskError)
280
+ end
281
+ end
282
+ end
283
+
284
+ describe "detach the independent disk causing the storage profile problem" do
285
+ context "#detach_independent_disks" do
286
+ it "can detach independent disks from the VM" do
287
+ disk = @test_case_independent_disk_list.first
288
+ expect(disk.attached_vms.first.id).to eq(@vm.id)
289
+ @vm.detach_independent_disks(@test_case_independent_disk_list)
290
+ expect(disk.attached_vms).to be_empty
291
+ end
292
+ end
293
+ end
235
294
 
236
- it "can update the storage profile of a VM" do
237
- available_storage_profiles = Vcloud::Core::QueryRunner.new.run(
238
- 'orgVdcStorageProfile',
239
- filter: "vdcName==#{@test_params.vdc_1_name}"
240
- )
241
- if available_storage_profiles.size == 1
242
- pending("There is only one StorageProfile in vDC #{@test_params.vdc_1_name}: cannot test.")
295
+ describe "can now update the storage profile" do
296
+
297
+ context "#update_storage_profile" do
298
+
299
+ it "can update the storage profile of a VM" do
300
+ available_storage_profiles = Vcloud::Core::QueryRunner.new.run(
301
+ 'orgVdcStorageProfile',
302
+ filter: "vdcName==#{@test_params.vdc_1_name}"
303
+ )
304
+ if available_storage_profiles.size == 1
305
+ pending("There is only one StorageProfile in vDC #{@test_params.vdc_1_name}: cannot test.")
306
+ end
307
+ original_storage_profile_name = @vm.vcloud_attributes[:StorageProfile][:name]
308
+ expect(original_storage_profile_name).to eq(@test_params.default_storage_profile_name)
309
+ @vm.update_storage_profile(@test_params.storage_profile)
310
+ expect(@vm.vcloud_attributes[:StorageProfile][:name]).to eq(@test_params.storage_profile)
243
311
  end
244
- original_storage_profile_name = @vm.vcloud_attributes[:StorageProfile][:name]
245
- expect(original_storage_profile_name).to eq(@test_params.default_storage_profile_name)
246
- @vm.update_storage_profile(@test_params.storage_profile)
247
- expect(@vm.vcloud_attributes[:StorageProfile][:name]).to eq(@test_params.storage_profile)
312
+
248
313
  end
249
314
 
250
315
  end
251
316
 
317
+
252
318
  after(:all) do
253
319
  IntegrationHelper.delete_vapps(@test_case_vapps)
320
+ IntegrationHelper.delete_independent_disks(@test_case_independent_disk_list)
254
321
  end
255
322
 
256
323
  def get_vm_hard_disks(fog_model_vm)
@@ -32,6 +32,32 @@ module IntegrationHelper
32
32
  end
33
33
  end
34
34
 
35
+ def self.create_test_case_independent_disks(number_of_disks,
36
+ vdc_name,
37
+ size,
38
+ prefix = "vcloud-core-tests"
39
+ )
40
+ timestamp_in_s = Time.new.to_i
41
+ base_disk_name = "#{prefix}-#{timestamp_in_s}-"
42
+ disk_list = []
43
+ vdc = Vcloud::Core::Vdc.get_by_name(vdc_name)
44
+ number_of_disks.times do |index|
45
+ disk_list << Vcloud::Core::IndependentDisk.create(
46
+ vdc,
47
+ base_disk_name + index.to_s,
48
+ size,
49
+ )
50
+ end
51
+ disk_list
52
+ end
53
+
54
+ def self.delete_independent_disks(disk_list)
55
+ fsi = Vcloud::Core::Fog::ServiceInterface.new()
56
+ disk_list.each do |disk|
57
+ fsi.delete_disk(disk.id)
58
+ end
59
+ end
60
+
35
61
  def self.reset_edge_gateway(edge_gateway)
36
62
  configuration = {
37
63
  :FirewallService =>
@@ -1,4 +1,5 @@
1
1
  require 'ostruct'
2
+ require 'securerandom'
2
3
 
3
4
  class StubFogInterface
4
5
 
@@ -50,6 +51,14 @@ class StubFogInterface
50
51
  }
51
52
  end
52
53
 
54
+ def post_create_disk(_vdc_id, name, size)
55
+ {
56
+ :name => name,
57
+ :href => "https://api.example.com/disk/#{SecureRandom.uuid}",
58
+ :size => size,
59
+ }
60
+ end
61
+
53
62
  def get_vapp_by_vdc_and_name
54
63
  { }
55
64
  end
@@ -0,0 +1,239 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vcloud::Core::IndependentDisk do
4
+ before(:each) do
5
+ @disk_id = '12345678-1234-1234-1234-112112112112'
6
+ @disk_name = 'test-disk-1'
7
+ @vdc_name = 'test-vdc-1'
8
+ @mock_fog_interface = StubFogInterface.new
9
+ allow(Vcloud::Core::Fog::ServiceInterface).to receive(:new).and_return(@mock_fog_interface)
10
+ end
11
+
12
+ context "Class public interface" do
13
+ it { expect(Vcloud::Core::IndependentDisk).to respond_to(:get_by_name_and_vdc_name) }
14
+ end
15
+
16
+ context "Instance public interface" do
17
+ subject { Vcloud::Core::IndependentDisk.new(@disk_id) }
18
+ it { should respond_to(:id) }
19
+ it { should respond_to(:vcloud_attributes) }
20
+ it { should respond_to(:name) }
21
+ it { should respond_to(:href) }
22
+ it { should respond_to(:attached_vms) }
23
+ end
24
+
25
+ context "#initialize" do
26
+
27
+ it "should be constructable from just an id reference" do
28
+ obj = Vcloud::Core::IndependentDisk.new(@disk_id)
29
+ expect(obj.class).to be(Vcloud::Core::IndependentDisk)
30
+ end
31
+
32
+ it "should store the id specified" do
33
+ obj = Vcloud::Core::IndependentDisk.new(@disk_id)
34
+ expect(obj.id).to eq(@disk_id)
35
+ end
36
+
37
+ it "should raise error if id is not in correct format" do
38
+ bogus_id = 'foo-12314124-ede5-4d07-bad5-000000111111'
39
+ expect{
40
+ Vcloud::Core::IndependentDisk.new(bogus_id)
41
+ }.to raise_error("IndependentDisk id : #{bogus_id} is not in correct format" )
42
+ end
43
+
44
+ end
45
+
46
+ context "#get_by_name_and_vdc_name" do
47
+
48
+ it "should return a Disk object if disk is found" do
49
+ q_results = [
50
+ { :name => @disk_name, :href => @disk_id }
51
+ ]
52
+ mock_query = double(:query)
53
+ expect(Vcloud::Core::QueryRunner).to receive(:new).and_return(mock_query)
54
+ expect(mock_query).to receive(:run).with(
55
+ 'disk',
56
+ :filter => "name==#{@disk_name};vdcName==#{@vdc_name}"
57
+ ).and_return(q_results)
58
+ obj = Vcloud::Core::IndependentDisk.get_by_name_and_vdc_name(@disk_name, @vdc_name)
59
+ expect(obj.class).to be(Vcloud::Core::IndependentDisk)
60
+ end
61
+
62
+ it "should raise an error if no Independent Disk with that name exists" do
63
+ q_results = [ ]
64
+ mock_query = double(:query_runner)
65
+ expect(Vcloud::Core::QueryRunner).to receive(:new).and_return(mock_query)
66
+ expect(mock_query).to receive(:run).with(
67
+ 'disk',
68
+ :filter => "name==#{@disk_name};vdcName==#{@vdc_name}"
69
+ ).and_return(q_results)
70
+ expect {
71
+ Vcloud::Core::IndependentDisk.get_by_name_and_vdc_name(@disk_name, @vdc_name)
72
+ }.to raise_exception(Vcloud::Core::IndependentDisk::DiskNotFoundException)
73
+ end
74
+
75
+ it "should raise an error if multiple Independent Disks with " +
76
+ "that name exists (NB: prescribes unique disk names!)" do
77
+ q_results = [
78
+ { :name => @disk_name, :href => @disk_id },
79
+ { :name => @disk_name, :href => '12341234-1234-1234-1234-123456789012' },
80
+ ]
81
+ mock_query = double(:query)
82
+ expect(Vcloud::Core::QueryRunner).to receive(:new).and_return(mock_query)
83
+ expect(mock_query).to receive(:run).with(
84
+ 'disk',
85
+ :filter => "name==#{@disk_name};vdcName==#{@vdc_name}"
86
+ ).and_return(q_results)
87
+ expect {
88
+ Vcloud::Core::IndependentDisk.get_by_name_and_vdc_name(@disk_name, @vdc_name)
89
+ }.to raise_exception(RuntimeError)
90
+ end
91
+
92
+ end
93
+
94
+ describe "#convert_size_to_bytes" do
95
+
96
+ it "accepts integers, passing through as bytes" do
97
+ expect(Vcloud::Core::IndependentDisk.convert_size_to_bytes(100_000_000)).to eq(100_000_000)
98
+ end
99
+
100
+ it "accepts suffixless strings, passing through as bytes" do
101
+ expect(Vcloud::Core::IndependentDisk.convert_size_to_bytes('100000000')).to eq(100_000_000)
102
+ end
103
+
104
+ it "converts 100MB to 100_000_000 bytes" do
105
+ expect(Vcloud::Core::IndependentDisk.convert_size_to_bytes('100MB')).to eq(100_000_000)
106
+ end
107
+
108
+ it "converts 10MiB to 104_857_600 bytes" do
109
+ expect(Vcloud::Core::IndependentDisk.convert_size_to_bytes('100MiB')).to eq(104_857_600)
110
+ end
111
+
112
+ it "converts 10GB to 100_000_000_000 bytes" do
113
+ expect(Vcloud::Core::IndependentDisk.convert_size_to_bytes('100GB')).to eq(100_000_000_000)
114
+ end
115
+
116
+ it "converts 10GiB to 100_000_000_000 bytes" do
117
+ expect(Vcloud::Core::IndependentDisk.convert_size_to_bytes('100GiB')).to eq(107_374_182_400)
118
+ end
119
+
120
+ it "raises an ArgumentError if numeric component is not an integer" do
121
+ expect{Vcloud::Core::IndependentDisk.convert_size_to_bytes('10.5GB')}.
122
+ to raise_error(ArgumentError)
123
+ end
124
+
125
+ it "raises an ArgumentError if it does not understand the input" do
126
+ expect{Vcloud::Core::IndependentDisk.convert_size_to_bytes('10wibbles')}.
127
+ to raise_error(ArgumentError)
128
+ end
129
+
130
+ end
131
+
132
+ describe "#create" do
133
+
134
+ let(:vdc) { double(:vdc, :id => "12341234-1234-1234-1234-123412341234", :name => @vdc_name)}
135
+
136
+ context "when there is no disk already present with that name" do
137
+
138
+ before(:each) do
139
+ mock_query = double(:query_runner)
140
+ expect(Vcloud::Core::QueryRunner).to receive(:new).and_return(mock_query)
141
+ expect(mock_query).to receive(:run).with(
142
+ 'disk',
143
+ :filter => "name==new-disk-1;vdcName==#{@vdc_name}"
144
+ ).and_return([])
145
+ end
146
+
147
+ it "returns an IndependentDisk object if successful" do
148
+ size_in_bytes = 1000_000_000
149
+ obj = Vcloud::Core::IndependentDisk.create(vdc, "new-disk-1", size_in_bytes)
150
+ expect(obj.class).to be(Vcloud::Core::IndependentDisk)
151
+ end
152
+
153
+ it "handles size parameter suffixes (MB, GB, ...)" do
154
+ size = "100MB"
155
+ expect(@mock_fog_interface).to receive(:post_create_disk).with(
156
+ vdc.id, "new-disk-1", 100_000_000
157
+ ).and_return({ :href => "/#{12341234-1234-1234-1234-123412341234}" })
158
+ obj = Vcloud::Core::IndependentDisk.create(vdc, "new-disk-1", size)
159
+ expect(obj.class).to be(Vcloud::Core::IndependentDisk)
160
+ end
161
+
162
+ it "handles size parameter given as an Integer (in bytes)" do
163
+ size = 100_000_000_000
164
+ expect(@mock_fog_interface).to receive(:post_create_disk).with(
165
+ vdc.id, "new-disk-1", 100_000_000_000
166
+ ).and_return({ :href => "/#{12341234-1234-1234-1234-123412341234}" })
167
+ obj = Vcloud::Core::IndependentDisk.create(vdc, "new-disk-1", size)
168
+ expect(obj.class).to be(Vcloud::Core::IndependentDisk)
169
+ end
170
+
171
+ end
172
+
173
+ context "when there is a disk present in the vDC with the same name" do
174
+
175
+ it "raises an error" do
176
+ mock_query = double(:query_runner)
177
+ q_results = [ { :name => @disk_name, :href => @disk_id } ]
178
+ expect(Vcloud::Core::QueryRunner).to receive(:new).and_return(mock_query)
179
+ expect(mock_query).to receive(:run).with(
180
+ 'disk',
181
+ :filter => "name==#{@disk_name};vdcName==#{@vdc_name}"
182
+ ).and_return(q_results)
183
+ expect{ Vcloud::Core::IndependentDisk.create(vdc, @disk_name, 100_000) }.
184
+ to raise_error(Vcloud::Core::IndependentDisk::DiskAlreadyExistsException)
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ context "attributes" do
192
+
193
+ before(:each) {
194
+ @stub_attrs = {
195
+ :name => @disk_name,
196
+ :href => "https://api.vcloud-director.example.com/api/disk/#{@disk_id}",
197
+ :Link => [{
198
+ :rel => 'up',
199
+ :type => 'application/vnd.vmware.vcloud.vdc+xml',
200
+ :href => 'https://api.vcloud-director.example.com/api/vdc/074aea1e-a5e9-4dd1-a028-40db8c98d237'
201
+ }]
202
+ }
203
+ allow_any_instance_of(StubFogInterface).to receive(:get_disk).and_return(@stub_attrs)
204
+ @disk = Vcloud::Core::IndependentDisk.new(@disk_id)
205
+ }
206
+
207
+ it { expect(@disk.name).to eq(@disk_name) }
208
+ it { expect(@disk.id).to eq(@disk_id) }
209
+
210
+ end
211
+
212
+ context "#attached_vms" do
213
+
214
+ subject { Vcloud::Core::IndependentDisk.new(@disk_id) }
215
+
216
+ it "returns an empty list if there are no attached vms" do
217
+ expect(@mock_fog_interface).to receive(:get_vms_disk_attached_to).
218
+ with(subject.id).and_return({:VmReference=>[]})
219
+ expect(subject.attached_vms).to eq([])
220
+ end
221
+
222
+ it "returns a list of Core::Vm objects that are attached" do
223
+ expect(Vcloud::Core::Vapp).to receive(:get_by_child_vm_id).exactly(2).times.and_return({
224
+ :href => "/vapp-12341234-1234-1234-1234-123412340000"
225
+ })
226
+ expect(@mock_fog_interface).to receive(:get_vms_disk_attached_to).
227
+ with(subject.id).and_return({:VmReference=>[
228
+ { :href => '/vm-12341234-1234-1234-1234-123412340001' },
229
+ { :href => '/vm-12341234-1234-1234-1234-123412340002' },
230
+ ]})
231
+ vms = subject.attached_vms
232
+ expect(vms[0].id).to eq('vm-12341234-1234-1234-1234-123412340001')
233
+ expect(vms[1].id).to eq('vm-12341234-1234-1234-1234-123412340002')
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+
@@ -175,6 +175,34 @@ module Vcloud
175
175
 
176
176
  end
177
177
 
178
+ context "#get_by_child_vm_id" do
179
+
180
+ it "should raise an ArgumentError if an invalid VM id is supplied" do
181
+ vm_id = 'vapp-12341234-1234-1234-1234-123412341234'
182
+ expect {Vapp.get_by_child_vm_id(vm_id)}.to raise_error(ArgumentError)
183
+ end
184
+
185
+ it "should return a vApp object if we supply an existing VM id" do
186
+ vm_id = "vm-12341234-1234-1234-1234-123412340001"
187
+ vapp_id = "vapp-12341234-1234-1234-1234-123412349999"
188
+ expect(@mock_fog_interface).to receive(:get_vapp).with(vm_id).and_return({
189
+ :Link => [
190
+ { :rel => 'down',
191
+ :type => "application/vnd.vmware.vcloud.metadata+xml",
192
+ :href => "/api/vApp/#{vm_id}/metadata"
193
+ },
194
+ { :rel => 'up',
195
+ :type => "application/vnd.vmware.vcloud.vApp+xml",
196
+ :href => "/api/vApp/#{vapp_id}"
197
+ }
198
+ ]
199
+ })
200
+ obj = Vapp.get_by_child_vm_id(vm_id)
201
+ expect(obj.id).to eq(vapp_id)
202
+ end
203
+
204
+ end
205
+
178
206
  end
179
207
  end
180
208
  end
@@ -56,6 +56,8 @@ module Vcloud
56
56
  it { should respond_to(:update_metadata) }
57
57
  it { should respond_to(:update_storage_profile) }
58
58
  it { should respond_to(:add_extra_disks) }
59
+ it { should respond_to(:attach_independent_disks) }
60
+ it { should respond_to(:detach_independent_disks) }
59
61
  it { should respond_to(:configure_network_interfaces) }
60
62
  it { should respond_to(:configure_guest_customization_section) }
61
63
  end
@@ -327,6 +329,48 @@ module Vcloud
327
329
 
328
330
  end
329
331
 
332
+ context "#attach_independent_disks" do
333
+
334
+ let(:disk1) { double(:disk, :name => 'test-disk-1',
335
+ :id => '12341234-1234-1234-1234-12345678900')
336
+ }
337
+ let(:disk2) { double(:disk, :name => 'test-disk-2',
338
+ :id => '12341234-1234-1234-1234-12345678901')
339
+ }
340
+ let(:disk3) { double(:disk, :name => 'test-disk-3',
341
+ :id => '12341234-1234-1234-1234-12345678902')
342
+ }
343
+
344
+ it "handles attaching an array of Independent Disk objects" do
345
+ vm = Vm.new(@vm_id, @mock_vapp)
346
+ disk_array = [disk1, disk2, disk3]
347
+ expect(@fog_interface).to receive(:post_attach_disk).exactly(disk_array.size).times
348
+ vm.attach_independent_disks(disk_array)
349
+ end
350
+
351
+ end
352
+
353
+ context "#detach_independent_disks" do
354
+
355
+ let(:disk1) { double(:disk, :name => 'test-disk-1',
356
+ :id => '12341234-1234-1234-1234-12345678900')
357
+ }
358
+ let(:disk2) { double(:disk, :name => 'test-disk-2',
359
+ :id => '12341234-1234-1234-1234-12345678901')
360
+ }
361
+ let(:disk3) { double(:disk, :name => 'test-disk-3',
362
+ :id => '12341234-1234-1234-1234-12345678902')
363
+ }
364
+
365
+ it "handles detaching an array of Independent Disk objects" do
366
+ vm = Vm.new(@vm_id, @mock_vapp)
367
+ disk_array = [disk1, disk2, disk3]
368
+ expect(@fog_interface).to receive(:post_detach_disk).exactly(disk_array.size).times
369
+ vm.detach_independent_disks(disk_array)
370
+ end
371
+
372
+ end
373
+
330
374
  end
331
375
  end
332
376
  end
data/vcloud-core.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.required_ruby_version = '>= 1.9.3'
24
24
 
25
- s.add_runtime_dependency 'fog', '>= 1.22.0'
25
+ s.add_runtime_dependency 'fog', '>= 1.23.0'
26
26
  s.add_runtime_dependency 'mustache'
27
27
  s.add_runtime_dependency 'highline'
28
28
  s.add_development_dependency 'gem_publisher', '1.2.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vcloud-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-11 00:00:00.000000000 Z
12
+ date: 2014-10-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
16
- requirement: &5442080 !ruby/object:Gem::Requirement
16
+ requirement: &8805340 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 1.22.0
21
+ version: 1.23.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *5442080
24
+ version_requirements: *8805340
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: mustache
27
- requirement: &5461800 !ruby/object:Gem::Requirement
27
+ requirement: &8804180 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *5461800
35
+ version_requirements: *8804180
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: highline
38
- requirement: &5459000 !ruby/object:Gem::Requirement
38
+ requirement: &8803000 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *5459000
46
+ version_requirements: *8803000
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: gem_publisher
49
- requirement: &5455320 !ruby/object:Gem::Requirement
49
+ requirement: &8821020 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - =
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.2.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *5455320
57
+ version_requirements: *8821020
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: pry
60
- requirement: &5473200 !ruby/object:Gem::Requirement
60
+ requirement: &8820120 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *5473200
68
+ version_requirements: *8820120
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
- requirement: &5471980 !ruby/object:Gem::Requirement
71
+ requirement: &8819460 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *5471980
79
+ version_requirements: *8819460
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rspec
82
- requirement: &5469500 !ruby/object:Gem::Requirement
82
+ requirement: &8818720 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 2.14.1
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *5469500
90
+ version_requirements: *8818720
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: rubocop
93
- requirement: &5496580 !ruby/object:Gem::Requirement
93
+ requirement: &8813100 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ~>
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: 0.23.0
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *5496580
101
+ version_requirements: *8813100
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: simplecov
104
- requirement: &5495720 !ruby/object:Gem::Requirement
104
+ requirement: &8897220 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ~>
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: 0.7.1
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *5495720
112
+ version_requirements: *8897220
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: vcloud-tools-tester
115
- requirement: &5493500 !ruby/object:Gem::Requirement
115
+ requirement: &8895540 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ~>
@@ -120,7 +120,7 @@ dependencies:
120
120
  version: 0.2.0
121
121
  type: :development
122
122
  prerelease: false
123
- version_requirements: *5493500
123
+ version_requirements: *8895540
124
124
  description: Core tools for interacting with VMware vCloud Director. Includes VCloud
125
125
  Query, a light wrapper round the vCloud Query API.
126
126
  email:
@@ -156,6 +156,7 @@ files:
156
156
  - lib/vcloud/core/fog/login.rb
157
157
  - lib/vcloud/core/fog/model_interface.rb
158
158
  - lib/vcloud/core/fog/service_interface.rb
159
+ - lib/vcloud/core/independent_disk.rb
159
160
  - lib/vcloud/core/login_cli.rb
160
161
  - lib/vcloud/core/metadata_helper.rb
161
162
  - lib/vcloud/core/org_vdc_network.rb
@@ -170,6 +171,7 @@ files:
170
171
  - spec/integration/README.md
171
172
  - spec/integration/core/edge_gateway_spec.rb
172
173
  - spec/integration/core/fog/login_spec.rb
174
+ - spec/integration/core/independent_disk_spec.rb
173
175
  - spec/integration/core/query_runner_spec.rb
174
176
  - spec/integration/core/vapp_spec.rb
175
177
  - spec/integration/core/vdc_spec.rb
@@ -194,6 +196,7 @@ files:
194
196
  - spec/vcloud/core/fog/login_spec.rb
195
197
  - spec/vcloud/core/fog/service_interface_spec.rb
196
198
  - spec/vcloud/core/fog_spec.rb
199
+ - spec/vcloud/core/independent_disk_spec.rb
197
200
  - spec/vcloud/core/login_cli_spec.rb
198
201
  - spec/vcloud/core/metadata_helper_spec.rb
199
202
  - spec/vcloud/core/org_vdc_network_spec.rb
@@ -226,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
229
  version: '0'
227
230
  segments:
228
231
  - 0
229
- hash: 728476648198716353
232
+ hash: -425281742654841245
230
233
  requirements: []
231
234
  rubyforge_project:
232
235
  rubygems_version: 1.8.11
@@ -237,6 +240,7 @@ test_files:
237
240
  - spec/integration/README.md
238
241
  - spec/integration/core/edge_gateway_spec.rb
239
242
  - spec/integration/core/fog/login_spec.rb
243
+ - spec/integration/core/independent_disk_spec.rb
240
244
  - spec/integration/core/query_runner_spec.rb
241
245
  - spec/integration/core/vapp_spec.rb
242
246
  - spec/integration/core/vdc_spec.rb
@@ -261,6 +265,7 @@ test_files:
261
265
  - spec/vcloud/core/fog/login_spec.rb
262
266
  - spec/vcloud/core/fog/service_interface_spec.rb
263
267
  - spec/vcloud/core/fog_spec.rb
268
+ - spec/vcloud/core/independent_disk_spec.rb
264
269
  - spec/vcloud/core/login_cli_spec.rb
265
270
  - spec/vcloud/core/metadata_helper_spec.rb
266
271
  - spec/vcloud/core/org_vdc_network_spec.rb