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,308 @@
1
+ require 'fog'
2
+
3
+ module Dopv
4
+ module Infrastructure
5
+ class OpenStack < Base
6
+ extend Forwardable
7
+
8
+ def_delegator :@plan, :flavor, :flavor_name
9
+
10
+ def initialize(node_config, data_disks_db)
11
+ super(node_config, data_disks_db)
12
+
13
+
14
+ @compute_connection_opts = {
15
+ :provider => 'openstack',
16
+ :openstack_username => provider_username,
17
+ :openstack_api_key => provider_password,
18
+ :openstack_project_name => provider_tenant,
19
+ :openstack_domain_id => provider_domain_id,
20
+ :openstack_auth_url => provider_url,
21
+ :openstack_endpoint_type => provider_endpoint_type,
22
+ :connection_options => {
23
+ :ssl_verify_peer => false,
24
+ #:debug_request => true
25
+ }
26
+ }
27
+
28
+ @network_connection_opts = @compute_connection_opts
29
+ @volume_connection_opts = @compute_connection_opts
30
+
31
+ @node_creation_opts = {
32
+ :name => nodename,
33
+ :image_ref => template.id,
34
+ :flavor_ref => flavor.id,
35
+ :config_drive => use_config_drive?,
36
+ :security_groups => security_groups
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ def provider_tenant
43
+ @provider_tenant ||= infrastructure_properties.tenant
44
+ end
45
+
46
+ def provider_domain_id
47
+ @provider_domain_id ||= infrastructure_properties.domain_id
48
+ end
49
+
50
+ def provider_endpoint_type
51
+ @provider_endpoint_type ||= infrastructure_properties.endpoint_type
52
+ end
53
+
54
+ def use_config_drive?
55
+ @use_config_drive ||= infrastructure_properties.use_config_drive?
56
+ end
57
+
58
+ def security_groups
59
+ @security_groups ||= infrastructure_properties.security_groups
60
+ end
61
+
62
+ def network_provider
63
+ Dopv::log.info("Node #{nodename}: Creating network provider.") unless @network_provider
64
+ @network_provider ||= @network_connection_opts ? ::Fog::Network.new(@network_connection_opts) : nil
65
+ end
66
+
67
+ def flavor(filters={})
68
+ @flavor ||= compute_provider.flavors(filters).find { |f| f.name == flavor_name }
69
+ raise ProviderError, "No such flavor #{flavor_name}" unless @flavor
70
+ @flavor
71
+ end
72
+
73
+ def network(name, filters={})
74
+ net = network_provider.networks(filters).find { |n| n.name == name || n.id == name }
75
+ raise ProviderError, "No such network #{name}" unless net
76
+ net
77
+ end
78
+
79
+ def subnet(name, filters={})
80
+ net = network_provider.subnets(filters).find { |s| s.name == name || s.id == name }
81
+ raise ProviderError, "No such subnetwork #{name}" unless net
82
+ net
83
+ end
84
+
85
+ def assign_security_groups(node_instance)
86
+ unless security_groups.empty?
87
+ Dopv::log.info("Node #{nodename}: Assigning security groups.")
88
+ config_sgs = security_groups.dup
89
+ node_instance.security_groups.uniq { |g| g.id }.each do |sg|
90
+ # Remove the security group from configuration if it is already
91
+ # assigned to an instance.
92
+ if config_sgs.delete(sg.name)
93
+ Dopv::log.debug("Node #{nodename}: Already assigned to security group #{sg.name}.")
94
+ next
95
+ end
96
+ # Remove the security group assignment if it isn't in the
97
+ # configuration.
98
+ unless config_sgs.include?(sg.name)
99
+ Dopv::log.debug("Node #{nodename}: Removing unneeded security group #{sg.name}.")
100
+ compute_provider.remove_security_group(node_instance.id, sg.name)
101
+ wait_for_task_completion(node_instance)
102
+ end
103
+ end
104
+ # Add remaining security groups defined in config array.
105
+ config_sgs.each do |sg_name|
106
+ begin
107
+ Dopv::log.debug("Node #{nodename}: Adding security group #{sg_name}.")
108
+ compute_provider.add_security_group(node_instance.id, sg_name)
109
+ wait_for_task_completion(node_instance)
110
+ rescue
111
+ raise ProviderError, "An error occured while assigning security group #{sg_name}"
112
+ end
113
+ end
114
+ node_instance.reload
115
+ end
116
+ node_instance.security_groups
117
+ end
118
+
119
+ def node_instance_stopped?(node_instance)
120
+ !node_instance.ready?
121
+ end
122
+
123
+ def wait_for_task_completion(node_instance)
124
+ node_instance.wait_for { ready? }
125
+ end
126
+
127
+ def create_node_instance
128
+ Dopv::log.info("Node #{nodename}: Creating node instance.")
129
+
130
+ @node_creation_opts[:nics] = add_node_network_ports
131
+ @node_creation_opts[:user_data_encoded] = [cloud_config].pack('m')
132
+
133
+ Dopv::log.debug("Node #{nodename}: Spawning node instance.")
134
+ instance = compute_provider.servers.create(@node_creation_opts)
135
+ wait_for_task_completion(instance)
136
+ instance.reload
137
+
138
+ assign_security_groups(instance)
139
+
140
+ instance
141
+ end
142
+
143
+ def destroy_node_instance(node_instance, destroy_data_volumes=false)
144
+ remove_node_floating_ips(node_instance)
145
+ remove_node_network_ports(node_instance)
146
+ super(node_instance, destroy_data_volumes)
147
+ end
148
+
149
+ def start_node_instance(node_instance)
150
+ end
151
+
152
+ def stop_node_instance(node_instance)
153
+ super(node_instance)
154
+ node_instance.wait_for { !ready? }
155
+ end
156
+
157
+ def add_node_volume(node_instance, config)
158
+ volume = super(
159
+ compute_provider, {
160
+ :name => config.name,
161
+ :display_name => config.name,
162
+ :size => config.size.gibibytes.to_i,
163
+ :volume_type => config.pool,
164
+ :description => config.name
165
+ }
166
+ )
167
+ volume.wait_for { ready? }
168
+ attach_node_volume(node_instance, volume.reload)
169
+ volume.reload
170
+ end
171
+
172
+ def destroy_node_volume(node_instance, volume)
173
+ volume_instance = detach_node_volume(node_instance, volume)
174
+ volume_instance.destroy
175
+ node_instance.volumes.all({}).reload
176
+ end
177
+
178
+ def attach_node_volume(node_instance, volume)
179
+ volume_instance = node_instance.volumes.all({}).find { |v| v.id = volume.id }
180
+ node_instance.attach_volume(volume_instance.id, nil)
181
+ volume_instance.wait_for { volume_instance.status.downcase == "in-use" }
182
+ volume_instance
183
+ end
184
+
185
+ def detach_node_volume(node_instance, volume)
186
+ volume_instance = node_instance.volumes.all({}).find { |v| v.id = volume.id }
187
+ node_instance.detach_volume(volume_instance.id)
188
+ volume_instance.wait_for { volume_instance.status.downcase == "available" }
189
+ volume_instance
190
+ end
191
+
192
+ def record_node_data_volume(volume)
193
+ super(
194
+ :name => volume.name,
195
+ :id => volume.id,
196
+ :pool => volume.type == 'None' ? nil : volume.type,
197
+ :size => volume.size * 1073741824 # Returned in gibibytes
198
+ )
199
+ end
200
+
201
+ def fixed_ip(subnet_id, ip_address)
202
+ rval = { :subnet_id => subnet_id }
203
+ [:dhcp, :none].include?(ip_address) ? rval : rval.merge(:ip_address => ip_address)
204
+ end
205
+
206
+ def add_node_network_port(attrs)
207
+ ::Dopv::log.debug("Node #{nodename}: Adding network port #{attrs[:name]}.")
208
+ network_provider.ports.create(attrs)
209
+ end
210
+
211
+ def add_node_network_ports
212
+ ::Dopv::log.info("Node #{nodename}: Adding network ports.")
213
+ ports_config = {}
214
+ interfaces_config.each do |i|
215
+ s = subnet(i.network)
216
+ port_name = "#{nodename}_#{s.network_id}"
217
+ if ports_config.has_key?(port_name)
218
+ ports_config[port_name][:fixed_ips] << fixed_ip(s.id, i.ip)
219
+ else
220
+ ports_config[port_name] = {
221
+ :network_id => s.network_id,
222
+ :fixed_ips => [fixed_ip(s.id, i.ip)]
223
+ }
224
+ end
225
+ end
226
+ @network_ports = ports_config.map { |k,v| add_node_network_port(v.merge(:name => k)) }
227
+ @network_ports.collect { |p| {:net_id => p.network_id, :port_id => p.id} } # Net ID is required in Liberty++
228
+ end
229
+
230
+ def remove_node_network_ports(node_instance)
231
+ ::Dopv::log.warn("Node #{nodename}: Removing network ports.")
232
+ @network_ports ||= network_provider.ports.select { |p| p.device_id == node_instance.id } rescue {}
233
+ @network_ports.each { |p| p.destroy rescue nil } # TODO: dangerous, rewrite
234
+ @network_ports = {}
235
+ end
236
+
237
+ def add_node_floating_ip(attrs)
238
+ ::Dopv::log.debug("Node #{nodename}: Adding floating IP to #{attrs[:nicname]}.")
239
+ network_provider.floating_ips.create(attrs)
240
+ end
241
+
242
+ def add_node_floating_ips(node_instance)
243
+ ::Dopv::log.info("Node #{nodename}: Adding floating IPs.")
244
+ @network_ports ||= network_provider.ports.select { |p| p.device_id == node_instance.id }
245
+ interfaces_config.each do |i|
246
+ if i.floating_network
247
+ floating_network = network(i.floating_network)
248
+ subnetwork = subnet(i.network)
249
+ port = @network_ports.find { |p| p.fixed_ips.find { |f| f["subnet_id"] == subnetwork.id } }
250
+ attrs = {
251
+ :floating_network_id => floating_network.id,
252
+ :port_id => port.id,
253
+ :fixed_ip_address => port.fixed_ips.first["ip_address"],
254
+ :nicname => i.name
255
+ }
256
+ add_node_floating_ip(attrs)
257
+ end
258
+ end
259
+ end
260
+ alias_method :add_node_nics, :add_node_floating_ips
261
+
262
+ def remove_node_floating_ips(node_instance)
263
+ ::Dopv::log.warn("Node #{nodename}: Removing floating IPs.")
264
+ if node_instance
265
+ floating_ips = network_provider.floating_ips.select do |f|
266
+ node_instance.floating_ip_addresses.include?(f.floating_ip_address)
267
+ end
268
+ floating_ips.each { |f| f.destroy rescue nil } # TODO: dangerous, rewrite
269
+ end
270
+ end
271
+ alias_method :remove_node_nics, :remove_node_floating_ips
272
+
273
+ def cloud_config
274
+ config = "#cloud-config\n" \
275
+ "hostname: #{hostname}\n" \
276
+ "fqdn: #{fqdn}\n" \
277
+ "manage_etc_hosts: True\n" \
278
+ "ssh_pwauth: True\n"
279
+
280
+ if root_password
281
+ config << \
282
+ "chpasswd:\n" \
283
+ " list: |\n" \
284
+ " root:#{root_password}\n" \
285
+ " expire: False\n"
286
+ end
287
+
288
+ unless root_ssh_pubkeys.empty?
289
+ config << \
290
+ "users:\n" \
291
+ " - name: root\n" \
292
+ " ssh_authorized_keys:\n"
293
+ root_ssh_pubkeys.each { |k| config << " - #{k}\n" }
294
+ end
295
+
296
+ config <<
297
+ "runcmd:\n" \
298
+ " - service network restart\n"
299
+
300
+ config
301
+ end
302
+
303
+ def get_node_ip_addresses(node_instance)
304
+ (node_instance.ip_addresses + [node_instance.floating_ip_address]).flatten.uniq.compact
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,228 @@
1
+ require 'fog'
2
+ require 'uri'
3
+ require 'open-uri'
4
+
5
+ module Dopv
6
+ module Infrastructure
7
+ class Ovirt < Base
8
+ def initialize(plan, data_disks_db)
9
+ super(plan, data_disks_db)
10
+
11
+ @compute_connection_opts = {
12
+ :provider => 'ovirt',
13
+ :ovirt_username => provider_username,
14
+ :ovirt_password => provider_password,
15
+ :ovirt_url => provider_url,
16
+ :ovirt_ca_cert_file => provider_ca_cert_file
17
+ }
18
+
19
+ @node_creation_opts = {
20
+ :name => nodename,
21
+ :template => template.id,
22
+ :cores => cores,
23
+ :memory => memory.bytes,
24
+ :storage => storage.bytes,
25
+ :cluster => cluster.id,
26
+ :ha => keep_ha?,
27
+ :clone => full_clone?,
28
+ :storagedomain_name => infrastructure_properties.default_pool
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def compute_provider
35
+ unless @compute_provider
36
+ super
37
+ ::Dopv::log.debug("Node #{nodename}: Recreating client with proper datacenter.")
38
+ @compute_connection_opts[:ovirt_datacenter] = datacenter[:id]
39
+ @compute_provider = ::Fog::Compute.new(@compute_connection_opts)
40
+ end
41
+ @compute_provider
42
+ end
43
+
44
+ def get_node_instance
45
+ super({:without_details => true})
46
+ end
47
+
48
+ def wait_for_task_completion(node_instance)
49
+ node_instance.wait_for { !locked? }
50
+ end
51
+
52
+ def create_node_instance
53
+ node_instance = super
54
+
55
+ # For each disk, set up wipe after delete flag
56
+ node_instance.volumes.each do |v|
57
+ ::Dopv::log.debug("Node #{nodename}: Setting wipe after delete for disk #{v.alias}.")
58
+ update_node_volume(node_instance, v, {:wipe_after_delete => true})
59
+ end
60
+
61
+ node_instance
62
+ end
63
+
64
+ def customize_node_instance(node_instance)
65
+ ::Dopv::log.info("Node #{nodename}: Customizing node.")
66
+ customization_opts = {
67
+ :hostname => fqdn,
68
+ :dns => dns.name_servers,
69
+ :domain => dns.search_domains,
70
+ :user => 'root',
71
+ :password => root_password,
72
+ :ssh_authorized_keys => root_ssh_pubkeys
73
+ }
74
+
75
+ customization_opts[:nicsdef] = interfaces_config.collect do |i|
76
+ nic = {}
77
+ nic[:nicname] = i.name
78
+ nic[:on_boot] = 'true'
79
+ nic[:boot_protocol] = case i.ip
80
+ when :dhcp
81
+ 'DHCP'
82
+ when :none
83
+ 'NONE'
84
+ else
85
+ 'STATIC'
86
+ end
87
+ unless [:dhcp, :none].include?(i.ip)
88
+ nic[:ip] = i.ip
89
+ nic[:netmask] = i.netmask
90
+ nic[:gateway] = i.gateway if i.set_gateway?
91
+ end
92
+ nic
93
+ end
94
+
95
+ customization_opts
96
+ end
97
+
98
+ def start_node_instance(node_instance)
99
+ customization_opts = super(node_instance)
100
+ node_instance.service.vm_start_with_cloudinit(
101
+ :id => node_instance.id,
102
+ :user_data => customization_opts
103
+ )
104
+ end
105
+
106
+ # Redefine until regexp in Fog::Compute::Ovirt::Server#stopped? is fixed
107
+ def stop_node_instance(node_instance)
108
+ super
109
+ node_instance.wait_for { status.downcase == 'down' }
110
+ end
111
+
112
+ def add_node_nic(node_instance, attrs)
113
+ nic = node_instance.add_interface(attrs)
114
+ node_instance.interfaces.reload
115
+ nic
116
+ end
117
+
118
+ def update_node_nic(node_instance, nic, attrs)
119
+ node_instance.update_interface(attrs.merge({:id => nic.id}))
120
+ node_instance.interfaces.reload
121
+ end
122
+
123
+ def add_node_nics(node_instance)
124
+ ::Dopv::log.info("Node #{nodename}: Trying to add interfaces.")
125
+
126
+ # Remove all interfaces defined by the template
127
+ remove_node_nics(node_instance) { |n, i| n.destroy_interface(:id => i.id) }
128
+
129
+ # fetch first network for our reservation dance
130
+ first_network = cluster.networks.first.name
131
+ # Reserve MAC addresses
132
+ (1..interfaces_config.size).each do |i|
133
+ name = "tmp#{i}"
134
+ ::Dopv::log.debug("Node #{nodename}: Creating interface #{name}.")
135
+ attrs = {
136
+ :name => name,
137
+ :network_name => first_network,
138
+ :plugged => true,
139
+ :linked => true
140
+ }
141
+ add_node_nic(node_instance, attrs)
142
+ end
143
+
144
+ # Rearrange interfaces by their MAC addresses and assign them into
145
+ # appropriate networks
146
+ ic = interfaces_config.reverse
147
+ node_instance.interfaces.sort_by do |n| n.mac
148
+ i = ic.pop
149
+ ::Dopv::log.debug("Node #{nodename}: Configuring interface #{n.name} (#{n.mac}) as #{i.name} in #{i.network}.")
150
+ attrs = {
151
+ :name => i.name,
152
+ :network_name => i.network,
153
+ }
154
+ update_node_nic(node_instance, n, attrs)
155
+ end
156
+ end
157
+
158
+ def add_node_affinity(node_instance, name)
159
+ affinity_group = compute_provider.affinity_groups.find { |g| g.name == name }
160
+ raise ProviderError, "No such affinity group #{name}" unless affinity_group
161
+ ::Dopv::log.info("Node #{nodename}: Adding node to affinity group #{name}.")
162
+ node_instance.add_to_affinity_group(:id => affinity_group.id)
163
+ end
164
+
165
+ def add_node_volume(node_instance, config)
166
+ storage_domain = compute_provider.storage_domains.find { |d| d.name == config.pool }
167
+ raise ProviderError, "No such storage domain #{storage_domain_name}" unless storage_domain
168
+
169
+ node_instance.add_volume(
170
+ {
171
+ :alias => config.name,
172
+ :size => config.size.bytes,
173
+ :bootable => 'false',
174
+ :wipe_after_delete => 'true',
175
+ :storage_domain => storage_domain.id
176
+ }.tap { |h| (h[:format] = 'raw'; h[:sparse] = 'false') unless config.thin? }
177
+ )
178
+ wait_for_task_completion(node_instance)
179
+ node_instance.volumes.find { |v| v.alias == config.name } # TODO: Rewrite with volume.reload if possible
180
+ end
181
+
182
+ def destroy_node_volume(node_instance, volume)
183
+ node_instance.destroy_volume(:id => volume.id)
184
+ wait_for_task_completion(node_instance)
185
+ node_instance.volumes.reload
186
+ end
187
+
188
+ def attach_node_volume(node_instance, volume)
189
+ node_instance.attach_volume(:id => volume.id)
190
+ wait_for_task_completion(node_instance)
191
+ node_instance.volumes.reload
192
+ end
193
+
194
+ def detach_node_volume(node_instance, volume)
195
+ node_instance.detach_volume(:id => volume.id)
196
+ wait_for_task_completion(node_instance)
197
+ node_instance.volumes.reload
198
+ end
199
+
200
+ def record_node_data_volume(volume)
201
+ super(
202
+ :name => volume.alias,
203
+ :id => volume.id,
204
+ :pool => volume.storage_domain,
205
+ :size => volume.size
206
+ )
207
+ end
208
+
209
+ def provider_ca_cert_file
210
+ uri = infrastructure.endpoint
211
+ local_ca_file = "#{TMP}/#{uri.host}_#{uri.port}_ca.crt"
212
+ remote_ca_file = "#{uri.scheme}://#{uri.host}:#{uri.port}/ca.crt"
213
+ unless File.exists?(local_ca_file)
214
+ begin
215
+ open(remote_ca_file, :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE) do |remote_ca|
216
+ local_ca = open(local_ca_file, 'w')
217
+ local_ca.write(remote_ca.read)
218
+ local_ca.close
219
+ end
220
+ rescue
221
+ raise ProviderError, "Cannot download CA certificate from #{provider_url}"
222
+ end
223
+ end
224
+ local_ca_file
225
+ end
226
+ end
227
+ end
228
+ end