dopv 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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