dopv 0.11.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/ChangeLog.md +456 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +260 -0
  7. data/Guardfile +22 -0
  8. data/LICENSE.txt +177 -0
  9. data/README.md +214 -0
  10. data/Rakefile +6 -0
  11. data/bin/dopv +4 -0
  12. data/dopv.gemspec +52 -0
  13. data/lib/dopv.rb +166 -0
  14. data/lib/dopv/cli.rb +54 -0
  15. data/lib/dopv/cli/command_add.rb +37 -0
  16. data/lib/dopv/cli/command_export.rb +26 -0
  17. data/lib/dopv/cli/command_import.rb +32 -0
  18. data/lib/dopv/cli/command_list.rb +18 -0
  19. data/lib/dopv/cli/command_remove.rb +29 -0
  20. data/lib/dopv/cli/command_run.rb +38 -0
  21. data/lib/dopv/cli/command_update.rb +35 -0
  22. data/lib/dopv/cli/command_validate.rb +30 -0
  23. data/lib/dopv/infrastructure.rb +40 -0
  24. data/lib/dopv/infrastructure/providers/baremetal.rb +12 -0
  25. data/lib/dopv/infrastructure/providers/base.rb +422 -0
  26. data/lib/dopv/infrastructure/providers/openstack.rb +308 -0
  27. data/lib/dopv/infrastructure/providers/ovirt.rb +228 -0
  28. data/lib/dopv/infrastructure/providers/vsphere.rb +322 -0
  29. data/lib/dopv/log.rb +14 -0
  30. data/lib/dopv/persistent_disk.rb +128 -0
  31. data/lib/dopv/plan.rb +17 -0
  32. data/lib/dopv/state_store.rb +87 -0
  33. data/lib/dopv/version.rb +3 -0
  34. data/spec/data/hooks/test_hook_script_1 +9 -0
  35. data/spec/data/hooks/test_hook_script_2 +10 -0
  36. data/spec/data/plans/test-plan-1.yaml +140 -0
  37. data/spec/spec_helper.rb +112 -0
  38. data/spec/unit/dopv/dopv_spec.rb +7 -0
  39. data/spec/unit/dopv/persistent_disk_spec.rb +38 -0
  40. data/spec/unit/dopv/plan_spec.rb +34 -0
  41. data/spec/unit/dopv/version_spec.rb +17 -0
  42. metadata +401 -0
@@ -0,0 +1,322 @@
1
+ require 'rbvmomi'
2
+ require 'digest/sha2'
3
+ require 'fog'
4
+
5
+ module Dopv
6
+ module Infrastructure
7
+
8
+ class Vsphere < Base
9
+ extend Forwardable
10
+
11
+ def_delegators :@plan, :product_id, :organization_name
12
+
13
+ def initialize(node_config, data_disks_db)
14
+ super(node_config, data_disks_db)
15
+
16
+ @compute_connection_opts = {
17
+ :provider => 'vsphere',
18
+ :vsphere_username => provider_username,
19
+ :vsphere_password => provider_password,
20
+ :vsphere_server => provider_host,
21
+ :vsphere_port => provider_port,
22
+ :vsphere_expected_pubkey_hash => provider_pubkey_hash
23
+ }
24
+
25
+ @node_creation_opts = {
26
+ 'name' => nodename,
27
+ 'datacenter' => datacenter.name,
28
+ 'template_path' => image,
29
+ 'dest_folder' => dest_folder || '',
30
+ 'numCPUs' => cores,
31
+ 'memoryMB' => memory.mebibytes.to_i
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def dest_folder
38
+ @dest_folder ||= infrastructure_properties.dest_folder
39
+ end
40
+
41
+ def timezone
42
+ super || '085'
43
+ end
44
+
45
+ def node_instance_stopped?(node_instance)
46
+ !node_instance.ready?
47
+ end
48
+
49
+ def create_node_instance
50
+ ::Dopv::log.info("Node #{nodename}: Creating node instance.")
51
+ @node_creation_opts['datastore'] = infrastructure_properties.default_pool unless infrastructure_properties.default_pool.nil?
52
+ vm = compute_provider.vm_clone(@node_creation_opts.merge(
53
+ 'power_on' => false,
54
+ 'wait' => true))
55
+ compute_provider.servers.get(vm['new_vm']['id'])
56
+ end
57
+
58
+ def customize_node_instance(node_instance)
59
+ ::Dopv::log.info("Node #{nodename}: Customizing node.")
60
+ # Settings related to each network interface
61
+ ip_settings = interfaces_config.collect do |i|
62
+ ip_setting = ::RbVmomi::VIM::CustomizationIPSettings.new
63
+ if i.ip == :dhcp
64
+ ip_setting.ip = ::RbVmomi::VIM::CustomizationDhcpIpGenerator.new
65
+ else
66
+ ip_setting.ip = ::RbVmomi::VIM::CustomizationFixedIp('ipAddress' => i.ip)
67
+ ip_setting.subnetMask = i.netmask
68
+ ip_setting.gateway = [i.gateway] if i.set_gateway?
69
+ end
70
+ ip_setting
71
+ end
72
+
73
+ # Adapters mapping
74
+ nic_setting_map = ip_settings.collect { |s| RbVmomi::VIM::CustomizationAdapterMapping.new('adapter' => s) }
75
+
76
+ # Global network settings
77
+ global_ip_settings = RbVmomi::VIM::CustomizationGlobalIPSettings.new(
78
+ :dnsServerList => dns.name_servers,
79
+ :dnsSuffixList => dns.search_domains
80
+ )
81
+
82
+ # Identity settings
83
+ identity_settings = case guest_id_to_os_family(node_instance)
84
+ when :linux
85
+ RbVmomi::VIM::CustomizationLinuxPrep.new(
86
+ :domain => domainname,
87
+ :hostName => RbVmomi::VIM::CustomizationFixedName.new(:name => hostname)
88
+ )
89
+ when :windows
90
+ password_settings = (RbVmomi::VIM::CustomizationPassword.new(
91
+ :plainText => true,
92
+ :value => administrator_password
93
+ ) rescue nil)
94
+ RbVmomi::VIM::CustomizationSysprep.new(
95
+ :guiRunOnce => nil,
96
+ :guiUnattended => RbVmomi::VIM::CustomizationGuiUnattended.new(
97
+ :autoLogon => false,
98
+ :autoLogonCount => 1,
99
+ :password => password_settings,
100
+ :timeZone => timezone
101
+ ),
102
+ :identification => RbVmomi::VIM::CustomizationIdentification.new(
103
+ :domainAdmin => nil,
104
+ :domainAdminPassword => nil,
105
+ :joinDomain => nil
106
+ ),
107
+ :userData => RbVmomi::VIM::CustomizationUserData.new(
108
+ :computerName => RbVmomi::VIM::CustomizationFixedName.new(:name => hostname),
109
+ :fullName => administrator_fullname,
110
+ :orgName => organization_name,
111
+ :productId => product_id
112
+ )
113
+ )
114
+ else
115
+ raise ProviderError, "Unsupported guest OS type"
116
+ end
117
+
118
+ custom_spec = RbVmomi::VIM::CustomizationSpec.new(
119
+ :identity => identity_settings,
120
+ :globalIPSettings => global_ip_settings,
121
+ :nicSettingMap => nic_setting_map
122
+ )
123
+ custom_spec.options = RbVmomi::VIM::CustomizationWinOptions.new(
124
+ :changeSID => true,
125
+ :deleteAccounts => false
126
+ ) if guest_id_to_os_family(node_instance) == :windows
127
+
128
+ custom_spec
129
+ end
130
+
131
+ def start_node_instance(node_instance)
132
+ customization_spec = super(node_instance)
133
+ customize_node_task(node_instance, customization_spec)
134
+ node_instance.start
135
+ end
136
+
137
+ def add_node_nics(node_instance)
138
+ ::Dopv::log.info("Node #{nodename}: Trying to add interfaces.")
139
+
140
+ # Remove all interfaces defined by the template
141
+ remove_node_nics(node_instance)
142
+
143
+ # Create interfaces from scratch
144
+ interfaces_config.each do |i|
145
+ log_msg = i.virtual_switch.nil? ?
146
+ "Node #{nodename}: Creating interface #{i.name} in #{i.network}." :
147
+ "Node #{nodename}: Creating interface #{i.name} in #{i.network} (#{i.virtual_switch})."
148
+ ::Dopv::log.debug(log_msg)
149
+ attrs = {
150
+ :name => i.name,
151
+ :datacenter => node_instance.datacenter,
152
+ :network => i.network,
153
+ :virtualswitch => i.virtual_switch,
154
+ :type => 'VirtualVmxnet3'
155
+ }
156
+ add_node_nic(node_instance, attrs)
157
+ end
158
+ end
159
+
160
+ def add_node_volume(node_instance, config)
161
+ volume = node_instance.volumes.create(
162
+ :datastore => config.pool,
163
+ :size => config.size.kibibytes.to_i,
164
+ :mode => 'persistent',
165
+ :thin => true
166
+ )
167
+ node_instance.volumes.reload
168
+ volume.name = config.name
169
+ volume
170
+ end
171
+
172
+ def destroy_node_volume(node_instance, volume)
173
+ volume_instance = node_instance.volumes.find { |v| v.filename == volume.id }
174
+ volume_instance.destroy
175
+ node_instance.volumes.reload
176
+ end
177
+
178
+ def attach_node_volume(node_instance, volume)
179
+ backing_info = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo.new(
180
+ :datastore => volume.pool,
181
+ :fileName => volume.id,
182
+ :diskMode => 'persistent'
183
+ )
184
+
185
+ virtual_disk = RbVmomi::VIM::VirtualDisk.new(
186
+ :controllerKey => node_instance.scsi_controller.key,
187
+ :unitNumber => node_instance.volumes.collect { |v| v.unit_number }.max + 1,
188
+ :key => -1,
189
+ :backing => backing_info,
190
+ :capacityInKB => volume.size * 1048576
191
+ )
192
+
193
+ device_spec = RbVmomi::VIM::VirtualDeviceConfigSpec.new(
194
+ :operation => :add,
195
+ :device => virtual_disk
196
+ )
197
+
198
+ config_spec = RbVmomi::VIM::VirtualMachineConfigSpec.new(:deviceChange => [device_spec])
199
+
200
+ reconfig_node_task(node_instance, config_spec)
201
+
202
+ node_instance.volumes.reload
203
+ end
204
+
205
+ def detach_node_volume(node_instance, volume)
206
+ volume = node_instance.volumes.all.find { |v| v.filename == volume.id }
207
+
208
+ virtual_disk = RbVmomi::VIM::VirtualDisk.new(
209
+ :controllerKey => volume.server.scsi_controller.key,
210
+ :unitNumber => volume.unit_number,
211
+ :key => volume.key,
212
+ :capacityInKB => volume.size
213
+ )
214
+
215
+ device_spec = RbVmomi::VIM::VirtualDeviceConfigSpec.new(
216
+ :operation => :remove,
217
+ :device => virtual_disk
218
+ )
219
+
220
+ config_spec = RbVmomi::VIM::VirtualMachineConfigSpec.new(:deviceChange => [device_spec])
221
+
222
+ reconfig_node_task(node_instance, config_spec)
223
+
224
+ node_instance.volumes.reload
225
+ end
226
+
227
+ def record_node_data_volume(volume)
228
+ ::Dopv::log.debug("Node #{nodename}: Recording volume #{volume.name} into DB.")
229
+ volume = {
230
+ :name => volume.name,
231
+ :id => volume.filename,
232
+ :pool => volume.datastore,
233
+ :size => volume.size * 1048576 # Size must be in gibibytes
234
+ }
235
+ super(volume)
236
+ end
237
+
238
+ def guest_id_to_os_family(node_instance)
239
+ # Based on http://pubs.vmware.com/vsphere-50/index.jsp?topic=/com.vmware.wssdk.apiref.doc_50/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html
240
+ lookup_table = {
241
+ :debian6_64Guest => :linux,
242
+ :debian6_Guest => :linux,
243
+ :rhel4_64Guest => :linux,
244
+ :rhel4Guest => :linux,
245
+ :rhel5_64Guest => :linux,
246
+ :rhel5Guest => :linux,
247
+ :rhel6_64Guest => :linux,
248
+ :rhel6Guest => :linux,
249
+ :rhel7_64Guest => :linux,
250
+ :rhel7Guest => :linux,
251
+ :oracleLinux64Guest => :linux,
252
+ :oracleLinuxGuest => :linux,
253
+ :ubuntu64Guest => :linux,
254
+ :ubuntuGuest => :linux,
255
+ :windows7_64Guest => :windows,
256
+ :windows7Guest => :windows,
257
+ :windows7Server64Guest => :windows,
258
+ :windows8_64Guest => :windows,
259
+ :windows8Guest => :windows,
260
+ :windows8Server64Guest => :windows
261
+ }
262
+
263
+ node_instance.guest_id ?
264
+ lookup_table[node_instance.guest_id.to_sym] : nil
265
+ end
266
+
267
+ def reconfig_node_task(node_instance, reconfig_spec)
268
+ node_ref = compute_provider.send(:get_vm_ref, node_instance.id)
269
+ node_ref.ReconfigVM_Task(:spec => reconfig_spec).wait_for_completion
270
+ end
271
+
272
+ def customize_node_task(node_instance, customization_spec)
273
+ node_ref = compute_provider.send(:get_vm_ref, node_instance.id)
274
+ node_ref.CustomizeVM_Task(:spec => customization_spec).wait_for_completion
275
+ end
276
+
277
+ def provider_pubkey_hash
278
+ unless @provider_pubkey_hash
279
+ connection = ::RbVmomi::VIM.new(
280
+ :host => provider_host,
281
+ :port => provider_port,
282
+ :ssl => provider_ssl?,
283
+ :ns => 'urn:vim25',
284
+ :rev => '4.0',
285
+ :insecure => true
286
+ )
287
+ @provider_pubkey_hash ||= ::Digest::SHA2.hexdigest(connection.http.peer_cert.public_key.to_s)
288
+ connection.close
289
+ end
290
+ @provider_pubkey_hash
291
+ end
292
+
293
+ def get_node_ip_addresses(node_instance, params={})
294
+ begin
295
+ raise ProviderError, "VMware Tools not installed" unless node_instance.tools_installed?
296
+ params = {:maxtime => 300, :delay => 10}.merge(params)
297
+ ::Dopv::log.debug("Node #{nodename}: Waiting on VMware Tools for #{params[:maxtime]} seconds.")
298
+ reload_node_instance(node_instance)
299
+ node_instance.wait_for(params[:maxtime]){|vm| vm.ready?}
300
+ node_instance.wait_for(params[:maxtime]){|vm| vm.tools_running?}
301
+ node_ref = compute_provider.send(:get_vm_ref, node_instance.id)
302
+ node_ref_guest_net = nil
303
+ start_time = Time.now.to_f
304
+ while (Time.now.to_f - start_time) < params[:maxtime]
305
+ unless node_ref.guest_ip
306
+ sleep params[:delay]
307
+ else
308
+ node_ref_guest_net = node_ref.guest.net
309
+ break
310
+ end
311
+ end
312
+ raise ProviderError, "VMware Tools not ready yet" unless node_ref_guest_net
313
+ node_ref_guest_net.map(&:ipAddress).flatten.uniq.compact
314
+
315
+ rescue Exception => e
316
+ ::Dopv::log.debug("Node #{nodename}: Unable to get all IP Addresses, Error: #{e.message}.")
317
+ [node_instance.public_ip_address].compact
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,14 @@
1
+ require 'logger'
2
+
3
+ module Dopv
4
+
5
+ def self.log
6
+ @log ||= DopCommon.log
7
+ end
8
+
9
+ def self.logger=(logger)
10
+ @log = logger
11
+ DopCommon.logger = logger
12
+ end
13
+
14
+ end
@@ -0,0 +1,128 @@
1
+ require 'yaml'
2
+
3
+ module Dopv
4
+ module PersistentDisk
5
+ class PersistentDiskError < StandardError; end
6
+
7
+ class Entry
8
+ DISK_DESC_KEYS = [:id, :name, :node, :pool, :size]
9
+
10
+ attr_accessor :name, :id, :pool, :size, :node
11
+
12
+ def initialize(attrs)
13
+ if attrs.is_a?(Hash) && attrs.keys.sort == DISK_DESC_KEYS
14
+ @node = attrs[:node]
15
+ @name = attrs[:name]
16
+ @id = attrs[:id]
17
+ @pool = attrs[:pool]
18
+ @size = attrs[:size].to_i
19
+ else
20
+ raise PersistentDiskError, "Invalid disk entry"
21
+ end
22
+ end
23
+
24
+ def ==(other)
25
+ case other
26
+ when Entry
27
+ @node == other.node && @name == other.name && @id == other.id && @pool == other.pool
28
+ when Hash
29
+ @node == other[:node] && @name == other[:name] && @id == other[:id] && @pool == other[:pool]
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def update(attrs={})
36
+ raise PersistentDiskError, "Update attributes must be a hash" unless attrs.is_a?(Hash)
37
+ @node = attrs[:node] if attrs[:node]
38
+ @name = attrs[:name] if attrs[:name]
39
+ @id = attrs[:id] if attrs[:id]
40
+ @pool = attrs[:pool] if attrs[:pool]
41
+ @size = attrs[:size].to_i if attrs[:size]
42
+ self
43
+ end
44
+
45
+ def to_s
46
+ "Disk: #{@name}\n Node: #{@node}\n Id: #{@id}\n Pool: #{@pool}\n Size: #{@size}"
47
+ end
48
+
49
+ def to_hash
50
+ { :name => @name, :id => @id, :pool => @pool, :size => @size }
51
+ end
52
+ end
53
+
54
+ class DB
55
+
56
+ def initialize(state_store, node_name)
57
+ @state_store = state_store
58
+ @node_name = node_name
59
+ @state_store.transaction do
60
+ @state_store[:data_volumes] ||= {}
61
+ @state_store[:data_volumes][@node_name] ||= []
62
+ end
63
+ end
64
+
65
+ def volumes
66
+ @state_store.transaction(true) { entries }.dup
67
+ end
68
+
69
+ def append(entry)
70
+ @state_store.transaction do
71
+ entries.each do |disk|
72
+ if disk == entry
73
+ raise PersistentDiskError, "Disk #{disk.name} already exists for node #{disk.node}"
74
+ end
75
+ end
76
+ if entry.is_a?(Entry)
77
+ @entries << entry
78
+ @state_store[:data_volumes][@node_name] << entry.to_hash
79
+ else
80
+ @entries << Entry.new(entry)
81
+ @state_store[:data_volumes][@node_name] << entry
82
+ end
83
+ end
84
+ end
85
+
86
+ def <<(entry)
87
+ append(entry)
88
+ end
89
+ alias_method :add, :<<
90
+
91
+ def update(entry, attrs={})
92
+ @state_store.transaction do
93
+ index = entries.index {|stored_entry| stored_entry = entry}
94
+ if index.nil?
95
+ raise PersistentDiskError, "Entry update: Disk entry not found #{entry.to_s}"
96
+ end
97
+ @entries[index].update(attrs)
98
+ @state_store[:data_volumes][@node_name][index] = @entries[index]
99
+ @entries[index]
100
+ end
101
+ end
102
+
103
+ def delete(entry)
104
+ @state_store.transaction do
105
+ index = entries.index {|stored_entry| stored_entry = entry}
106
+ if index.nil?
107
+ raise PersistentDiskError, "Entry update: Disk entry not found #{entry.to_s}"
108
+ end
109
+
110
+ @entries.delete_at(index)
111
+ @state_store[:data_volumes][@node_name].delete_at(index)
112
+ entry
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def entries
119
+ @entries ||= @state_store[:data_volumes][@node_name].map do |raw_entry|
120
+ symbolized_entry = Hash[raw_entry.map {|k1, v1| [k1.to_sym, v1] }]
121
+ merged_entry = symbolized_entry.merge({:node => @node_name})
122
+ Dopv::PersistentDisk::Entry.new(merged_entry)
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+ end