knife-vsphere 0.6.0 → 0.7.0

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.
@@ -107,6 +107,35 @@ class Chef
107
107
  @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
108
108
  end
109
109
 
110
+ def get_vm(vmname)
111
+ vim = get_vim_connection
112
+ baseFolder = find_folder(get_config(:folder));
113
+ retval = traverse_folders_for_vm(baseFolder,vmname)
114
+ return retval
115
+ end
116
+
117
+ def traverse_folders_for_vm(folder,vmname)
118
+ # not sure why @vm is necessary, but it returns class Array
119
+ # instead of class VirtualMachine without it... ugh
120
+ @vm = nil
121
+ folders = find_all_in_folder(folder, RbVmomi::VIM::Folder)
122
+ folders.each do |child|
123
+ traverse_folders_for_vm(child,vmname)
124
+ vms = find_all_in_folder(folder,RbVmomi::VIM::VirtualMachine)
125
+ vms.each do |vm|
126
+ if vm.name == vmname
127
+ @vm = vm
128
+ return @vm
129
+ end
130
+ end
131
+ end
132
+ return @vm
133
+ end
134
+
135
+ def get_datacenter
136
+ dc = config[:vim].serviceInstance.find_datacenter(get_config(:vsphere_dc)) or abort "datacenter not found"
137
+ end
138
+
110
139
  def find_folder(folderName)
111
140
  dcname = get_config(:vsphere_dc)
112
141
  dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
@@ -154,6 +183,53 @@ class Chef
154
183
  baseEntity
155
184
  end
156
185
 
186
+ def choose_datastore(dstores,size)
187
+ vmdk_size_kb = size.to_i * 1024 * 1024
188
+ vmdk_size_B = size.to_i * 1024 * 1024 * 1024
189
+
190
+ candidates = []
191
+ dstores.each do |store|
192
+ avail = number_to_human_size(store.summary[:freeSpace])
193
+ cap = number_to_human_size(store.summary[:capacity])
194
+ puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail}(#{store.summary[:freeSpace]}) / #{cap})"
195
+
196
+ # vm's can span multiple datastores, so instead of grabbing the first one
197
+ # let's find the first datastore with the available space on a LUN the vm
198
+ # is already using, or use a specified LUN (if given)
199
+
200
+
201
+ if ( store.summary[:freeSpace] - vmdk_size_B ) > 0
202
+ # also let's not use more than 90% of total space to save room for snapshots.
203
+ cap_remains = 100 * ( (store.summary[:freeSpace].to_f - vmdk_size_B.to_f ) / store.summary[:capacity].to_f )
204
+ if(cap_remains.to_i > 10)
205
+ candidates.push(store)
206
+ end
207
+ end
208
+ end
209
+ if candidates.length > 0
210
+ vmdk_datastore = candidates[0]
211
+ else
212
+ puts "Insufficient space on all LUNs designated or assigned to the virtual machine. Please specify a new target."
213
+ vmdk_datastore = nil
214
+ end
215
+ return vmdk_datastore
216
+ end
217
+
218
+
219
+ def find_datastores_regex(regex)
220
+ stores = Array.new()
221
+ puts "Looking for all datastores that match /#{regex}/"
222
+ dcname = get_config(:vsphere_dc)
223
+ dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
224
+ baseEntity = dc.datastore
225
+ baseEntity.each do |ds|
226
+ if ds.name.match /#{regex}/
227
+ stores.push ds
228
+ end
229
+ end
230
+ return stores
231
+ end
232
+
157
233
  def find_datastore(dsName)
158
234
  dcname = get_config(:vsphere_dc)
159
235
  dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
@@ -15,7 +15,12 @@ class Chef::Knife::VsphereVmVmdkAdd < Chef::Knife::BaseVsphereCommand
15
15
  option :vmdk_type,
16
16
  :long => "--vmdk-type TYPE",
17
17
  :description => "Type of VMDK"
18
- $default[:vmdk_type] = "thin"
18
+ # this is a bad idea as it will let you overcommit SAN by 400% or more. thick is a more "sane" default
19
+ $default[:vmdk_type] = "thin"
20
+
21
+ option :target_lun,
22
+ :long => "--target-lun NAME",
23
+ :description => "name of target LUN"
19
24
 
20
25
  def run
21
26
  $stdout.sync = true
@@ -37,42 +42,177 @@ class Chef::Knife::VsphereVmVmdkAdd < Chef::Knife::BaseVsphereCommand
37
42
  vim = get_vim_connection
38
43
  vdm = vim.serviceContent.virtualDiskManager
39
44
  vm = get_vm(vmname)
45
+ if vm.nil?
46
+ puts "Could not find #{vmname}"
47
+ return
48
+ end
40
49
 
41
- vmdk_datastore = vm.datastore[0]
42
- vmdk_fileName = "#{vmname}/#{vmname}_1.vmdk"
43
- vmdk_name = "[#{vmdk_datastore.name}] #{vmdk_fileName}"
50
+ target_lun = get_config(:target_lun) unless get_config(:target_lun).nil?
44
51
  vmdk_size_kb = size.to_i * 1024 * 1024
52
+ vmdk_size_B = size.to_i * 1024 * 1024 * 1024
53
+
54
+ if target_lun.nil?
55
+ vmdk_datastore = choose_datastore(vm.datastore,size)
56
+ exit -1 if vmdk_datastore.nil?
57
+ else
58
+ vmdk_datastores = find_datastores_regex(target_lun)
59
+ vmdk_datastore = choose_datastore(vmdk_datastores,size)
60
+ exit -1 if vmdk_datastore.nil?
61
+ vmdk_dir = "[#{vmdk_datastore.name}] #{vmname}"
62
+ # create the vm folder on the LUN or subsequent operations will fail.
63
+ if not vmdk_datastore.exists? vmname
64
+ dc = get_datacenter
65
+ dc._connection.serviceContent.fileManager.MakeDirectory :name => vmdk_dir, :datacenter => dc, :createParentDirectories => false
66
+ end
67
+ end
68
+
69
+ puts "Choosing: #{vmdk_datastore.name}"
70
+
71
+ # now we need to inspect the files in this datastore to get our next file name
72
+ next_vmdk = 1
73
+ pc = vmdk_datastore._connection.serviceContent.propertyCollector
74
+ vms = vmdk_datastore.vm
75
+ vmFiles = pc.collectMultiple vms, 'layoutEx.file'
76
+ vmFiles.keys.each do |vm|
77
+ vmFiles[vm]["layoutEx.file"].each do |layout|
78
+ if layout.name.match(/^\[#{vmdk_datastore.name}\] #{vmname}\/#{vmname}_([0-9]+).vmdk/)
79
+ num = $1
80
+ if next_vmdk <= num.to_i
81
+ next_vmdk = num.to_i + 1
82
+ end
83
+ end
84
+ end
85
+ end
86
+ vmdk_fileName = "#{vmname}/#{vmname}_#{next_vmdk}.vmdk"
87
+ vmdk_name = "[#{vmdk_datastore.name}] #{vmdk_fileName}"
45
88
  vmdk_type = get_config(:vmdk_type)
46
89
  vmdk_type = "preallocated" if vmdk_type == "thick"
90
+ puts "Next vmdk name is => #{vmdk_name}"
47
91
 
48
- vmdk_spec = RbVmomi::VIM::FileBackedVirtualDiskSpec(
49
- :adapterType => "lsiLogic",
50
- :capacityKb => vmdk_size_kb,
51
- :diskType => vmdk_type
52
- )
92
+ # create the disk
93
+ if not vmdk_datastore.exists? vmdk_fileName
94
+ vmdk_spec = RbVmomi::VIM::FileBackedVirtualDiskSpec(
95
+ :adapterType => "lsiLogic",
96
+ :capacityKb => vmdk_size_kb,
97
+ :diskType => vmdk_type
98
+ )
99
+ ui.info "Creating VMDK"
100
+ ui.info "#{ui.color "Capacity:", :cyan} #{size} GB"
101
+ ui.info "#{ui.color "Disk:", :cyan} #{vmdk_name}"
102
+
103
+ if get_config(:noop)
104
+ ui.info "#{ui.color "Skipping disk creation process because --noop specified.", :red}"
105
+ else
106
+ vdm.CreateVirtualDisk_Task(
107
+ :datacenter => get_datacenter,
108
+ :name => vmdk_name,
109
+ :spec => vmdk_spec
110
+ ).wait_for_completion
111
+ end
112
+ end
113
+ ui.info "Attaching VMDK to #{vmname}"
53
114
 
54
- ui.info "Creating VMDK"
55
- ui.info "#{ui.color "Capacity:", :cyan} #{size} GB"
56
- ui.info "#{ui.color "Disk:", :cyan} #{vmdk_name}"
115
+ # now we run through the SCSI controllers to see if there's an available one
116
+ available_controllers = Array.new()
117
+ use_controller = nil
118
+ scsi_tree = Hash.new()
119
+ vm.config.hardware.device.each do |device|
120
+ if device.class == RbVmomi::VIM::VirtualLsiLogicController
121
+ if scsi_tree[device.controllerKey].nil?
122
+ scsi_tree[device.key]=Hash.new()
123
+ scsi_tree[device.key]['children'] = Array.new();
124
+ scsi_tree[device.key]['device'] = device;
125
+ end
126
+ end
127
+ if device.class == RbVmomi::VIM::VirtualDisk
128
+ if scsi_tree[device.controllerKey].nil?
129
+ scsi_tree[device.controllerKey]=Hash.new()
130
+ scsi_tree[device.controllerKey]['children'] = Array.new();
131
+ end
132
+ scsi_tree[device.controllerKey]['children'].push(device)
133
+ end
134
+ end
135
+ scsi_tree.keys.sort.each do |controller|
136
+ if scsi_tree[controller]['children'].length < 15
137
+ available_controllers.push(scsi_tree[controller]['device'].deviceInfo.label)
138
+ end
139
+ end
57
140
 
58
- if get_config(:noop)
59
- ui.info "#{ui.color "Skipping disk creation process because --noop specified.", :red}"
141
+ if available_controllers.length > 0
142
+ use_controller = available_controllers[0]
143
+ puts "using #{use_controller}"
60
144
  else
61
- vdm.CreateVirtualDisk_Task(
62
- :datacenter => get_datacenter,
63
- :name => vmdk_name,
64
- :spec => vmdk_spec
65
- ).wait_for_completion
145
+
146
+ if scsi_tree.keys.length < 4
147
+
148
+ # Add a controller if none are available
149
+ puts "no controllers available. Will attempt to create"
150
+ new_scsi_key = scsi_tree.keys.sort[scsi_tree.length - 1] + 1
151
+ new_scsi_busNumber = scsi_tree[scsi_tree.keys.sort[scsi_tree.length - 1]]['device'].busNumber + 1
152
+
153
+ controller_device = RbVmomi::VIM::VirtualLsiLogicController(
154
+ :key => new_scsi_key,
155
+ :busNumber => new_scsi_busNumber,
156
+ :sharedBus => :noSharing
157
+ )
158
+
159
+ device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
160
+ :device => controller_device,
161
+ :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation("add")
162
+ )
163
+
164
+ vm_config_spec = RbVmomi::VIM::VirtualMachineConfigSpec(
165
+ :deviceChange => [device_config_spec]
166
+ )
167
+
168
+ if get_config(:noop)
169
+ ui.info "#{ui.color "Skipping controller creation process because --noop specified.", :red}"
170
+ else
171
+ vm.ReconfigVM_Task(:spec => vm_config_spec).wait_for_completion
172
+ end
173
+ else
174
+ ui.info "Controllers maxed out at 4."
175
+ exit -1;
176
+ end
177
+ end
178
+
179
+ # now go back and get the new device's name
180
+ vm.config.hardware.device.each do |device|
181
+ if device.class == RbVmomi::VIM::VirtualLsiLogicController
182
+ if device.key == new_scsi_key
183
+ use_controller = device.deviceInfo.label
184
+ end
185
+ end
66
186
  end
67
187
 
68
- ui.info "Attaching VMDK to #{vmname}"
188
+ # add the disk
189
+ controller = find_device(vm,use_controller)
190
+
191
+ used_unitNumbers = Array.new()
192
+ scsi_tree.keys.sort.each do |c|
193
+ if controller.key == scsi_tree[c]['device'].key
194
+ used_unitNumbers.push(scsi_tree[c]['device'].scsiCtlrUnitNumber)
195
+ scsi_tree[c]['children'].each do |disk|
196
+ used_unitNumbers.push(disk.unitNumber)
197
+ end
198
+ end
199
+ end
200
+ available_unitNumbers = Array.new
201
+ (0 .. 15).each do |scsi_id|
202
+ if used_unitNumbers.grep(scsi_id).length > 0
203
+ else
204
+ available_unitNumbers.push(scsi_id)
205
+ end
206
+ end
69
207
 
70
- controller = find_device(vm,"SCSI controller 0")
208
+ # ensure we don't try to add the controllers SCSI ID
209
+ new_unitNumber = available_unitNumbers.sort[0]
210
+ puts "using SCSI ID #{new_unitNumber}"
71
211
 
72
212
  vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo(
73
213
  :datastore => vmdk_datastore,
74
214
  :diskMode => "persistent",
75
- :fileName => vmdk_name,
215
+ :fileName => vmdk_name
76
216
  )
77
217
 
78
218
  device = RbVmomi::VIM::VirtualDisk(
@@ -80,7 +220,7 @@ class Chef::Knife::VsphereVmVmdkAdd < Chef::Knife::BaseVsphereCommand
80
220
  :capacityInKB => vmdk_size_kb,
81
221
  :controllerKey => controller.key,
82
222
  :key => -1,
83
- :unitNumber => controller.device.size + 1
223
+ :unitNumber => new_unitNumber
84
224
  )
85
225
 
86
226
  device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
@@ -1,4 +1,4 @@
1
- module KnifeVsphere
2
- VERSION = "0.6.0"
3
- end
4
-
1
+ module KnifeVsphere
2
+ VERSION = "0.7.0"
3
+ end
4
+
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: knife-vsphere
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.6.0
5
+ version: 0.7.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ezra Pagel
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2013-06-02 00:00:00 -05:00
13
+ date: 2013-06-05 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency