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,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