deltacloud-core 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/deltacloudd CHANGED
@@ -73,6 +73,9 @@ BANNER
73
73
  opts.on( '-t', '--timeout TIMEOUT', 'Timeout for single request (default: 60)') do |timeout|
74
74
  ENV["API_TIMEOUT"] = timeout
75
75
  end
76
+ opts.on( '-V', '--verbose', 'Set verbose logging on') do |verbose|
77
+ ENV["API_VERBOSE"] ||= 'true'
78
+ end
76
79
  opts.on( '-h', '--help', '') { options[:help] = true }
77
80
 
78
81
  opts.separator <<EOS
@@ -166,10 +169,6 @@ have_rerun = library_present?('rerun')
166
169
  unless have_thin
167
170
  require 'rack'
168
171
 
169
- # We can't chdir with webrick so add our root directory
170
- # onto the load path
171
- $: << dirname
172
-
173
172
  # Read in config.ru and convert it to an instance of Rack::Builder
174
173
  cfgfile = File.read(File.join(dirname, 'config.ru'))
175
174
  inner_app = eval("Rack::Builder.new {(" + cfgfile + "\n )}.to_app",
@@ -196,8 +195,7 @@ else
196
195
  argv_opts << ['start'] unless Thin::Runner.commands.include?(options[0])
197
196
  argv_opts << ['--address', ENV["API_HOST"] ]
198
197
  argv_opts << ['--port', ENV["API_PORT"] ]
199
- argv_opts << ['--rackup', 'config.ru' ]
200
- argv_opts << ['--chdir', dirname ]
198
+ argv_opts << ['--rackup', File.join(dirname, 'config.ru') ]
201
199
  argv_opts << ['-e', options[:env] ]
202
200
  argv_opts << ['--timeout', ENV["API_TIMEOUT"] || '60']
203
201
  argv_opts << ['--threaded', '-D' ]
@@ -208,7 +206,7 @@ else
208
206
  if options[:daemon]
209
207
  options[:env] = "production"
210
208
  argv_opts << [ "--daemonize", "--user", options[:user] || 'nobody', "--tag", "deltacloud-#{ENV['API_DRIVER']}"]
211
- argv << [ "--pid", options[:pid]] if options[:pid]
209
+ argv_opts << [ "--pid", options[:pid]] if options[:pid]
212
210
  end
213
211
  argv_opts.flatten!
214
212
 
@@ -220,6 +218,7 @@ else
220
218
  rerun.start
221
219
  rerun.join
222
220
  else
221
+ $:.unshift File.join(dirname)
223
222
  thin = Thin::Runner.new(argv_opts)
224
223
  thin.run!
225
224
  end
data/config.ru CHANGED
@@ -16,8 +16,10 @@
16
16
 
17
17
  require 'rubygems'
18
18
 
19
- $:.unshift File.join(File.dirname(__FILE__), '.')
19
+ top_srcdir = File.dirname(__FILE__)
20
20
 
21
- require 'server.rb'
21
+ $:.unshift File.join(top_srcdir, 'lib')
22
+
23
+ load File.join(top_srcdir, 'server.rb')
22
24
 
23
25
  run Sinatra::Application
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
29
29
  which implements the REST interface.
30
30
  EOF
31
31
 
32
- s.version = '0.4.0'
32
+ s.version = '0.4.1'
33
33
  s.date = Time.now
34
34
  s.summary = %q{Deltacloud REST API}
35
35
  s.files = FileList[
@@ -73,11 +73,26 @@ Gem::Specification.new do |s|
73
73
  s.add_dependency('json', '>= 1.1.9')
74
74
  s.add_dependency('net-ssh', '>= 2.0.0')
75
75
  s.add_dependency('thin', '>= 1.2.5')
76
- s.add_dependency('nokogiri', ">= 1.4.3")
77
- s.add_development_dependency('compass', '>= 0.8.17')
78
- s.add_development_dependency('nokogiri', '>= 1.4.1')
79
- s.add_development_dependency('rack-test', '>= 0.5.3')
80
- s.add_development_dependency('cucumber', '>= 0.6.3')
81
- s.add_development_dependency('rcov', '>= 0.9.8')
76
+ s.add_dependency('nokogiri', '>= 1.4.3')
77
+
78
+ # dependencies for various cloud providers:
79
+ # Amazon EC2 S3
80
+ s.add_dependency('aws', '>=2.5.4')
81
+ # Microsoft Azure
82
+ s.add_dependency('waz-storage', '>=1.1.0')
83
+
84
+ # Rackspace Cloudservers Cloudfiles
85
+ s.add_dependency('cloudservers')
86
+ s.add_dependency('cloudfiles')
87
+
88
+ # Terremark Vcloud Express
89
+ s.add_dependency('fog')
90
+ s.add_dependency('excon')
91
+
92
+ # Rhevm and Condor Cloud
93
+ s.add_dependency('rest-client')
94
+
95
+ # Condor Cloud
96
+ s.add_dependency('uuidtools', '>= 2.1.1')
82
97
 
83
98
  end
data/lib/deltacloud.rb ADDED
@@ -0,0 +1,27 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one or more
3
+ # contributor license agreements. See the NOTICE file distributed with
4
+ # this work for additional information regarding copyright ownership. The
5
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance with the
7
+ # License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations
15
+ # under the License.
16
+
17
+ require 'deltacloud/drivers'
18
+
19
+ require 'deltacloud/core_ext'
20
+
21
+ require 'deltacloud/base_driver'
22
+ require 'deltacloud/hardware_profile'
23
+ require 'deltacloud/state_machine'
24
+ require 'deltacloud/helpers'
25
+ require 'deltacloud/models'
26
+ require 'deltacloud/validation'
27
+ require 'deltacloud/runner'
@@ -16,3 +16,4 @@
16
16
 
17
17
  require 'deltacloud/core_ext/string'
18
18
  require 'deltacloud/core_ext/integer'
19
+ require 'deltacloud/core_ext/hash'
@@ -0,0 +1,29 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright ownership. The
4
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+
16
+ class Hash
17
+ def gsub_keys(rgx_pattern, replacement)
18
+ remove = []
19
+ self.each_key do |key|
20
+ if key.to_s.match(rgx_pattern)
21
+ new_key = key.to_s.gsub(rgx_pattern, replacement).downcase
22
+ self[new_key] = self[key]
23
+ remove << key
24
+ end
25
+ end
26
+ #remove the original keys
27
+ self.delete_if{|k,v| remove.include?(k)}
28
+ end
29
+ end
@@ -34,7 +34,8 @@ module Deltacloud
34
34
  def driver_config
35
35
  if Thread::current[:drivers].nil?
36
36
  Thread::current[:drivers] = {}
37
- Dir[File.join(File::dirname(__FILE__), '..', 'config', 'drivers', '*.yaml')].each do |driver_file|
37
+ top_srcdir = File.join(File.dirname(__FILE__), '..', '..')
38
+ Dir[File.join(top_srcdir, 'config', 'drivers', '*.yaml')].each do |driver_file|
38
39
  Thread::current[:drivers].merge!(YAML::load(File::read(driver_file)))
39
40
  end
40
41
  end
@@ -36,7 +36,7 @@ module Deltacloud
36
36
  module Condor
37
37
 
38
38
  require 'base64'
39
- require 'uuid'
39
+ require 'uuidtools'
40
40
  require 'fileutils'
41
41
 
42
42
  class CondorDriver < Deltacloud::BaseDriver
@@ -139,7 +139,7 @@ module Deltacloud
139
139
  config_server_address = nil
140
140
  end
141
141
  end
142
- vm_uuid ||= UUID::new.generate
142
+ vm_uuid ||= UUIDTools::UUID.random_create.to_s
143
143
  vm_otp ||= vm_uuid[0..7]
144
144
  new_client(credentials) do |condor|
145
145
  config_server_address ||= condor.ip_agent.address
@@ -212,21 +212,23 @@ module Deltacloud
212
212
  def create_instance(credentials, image_id, opts={})
213
213
  ec2 = new_client(credentials)
214
214
  instance_options = {}
215
- instance_options.merge!(:user_data => opts[:user_data]) if opts[:user_data]
216
- instance_options.merge!(:key_name => opts[:keyname]) if opts[:keyname]
217
- instance_options.merge!(:availability_zone => opts[:realm_id]) if opts[:realm_id]
218
- instance_options.merge!(:instance_type => opts[:hwp_id]) if opts[:hwp_id] && opts[:hwp_id].length > 0
215
+ if opts[:user_data]
216
+ instance_options[:user_data] = Base64::decode64(opts[:user_data])
217
+ end
218
+ instance_options[:key_name] = opts[:keyname] if opts[:keyname]
219
+ instance_options[:availability_zone] = opts[:realm_id] if opts[:realm_id]
220
+ instance_options[:instance_type] = opts[:hwp_id] if opts[:hwp_id] && opts[:hwp_id].length > 0
219
221
  firewalls = opts.inject([]){|res, (k,v)| res << v if k =~ /firewalls\d+$/; res}
220
- instance_options.merge!(:group_ids => firewalls ) unless firewalls.empty?
221
- instance_options.merge!(
222
- :min_count => opts[:instance_count],
223
- :max_count => opts[:instance_count]
224
- ) if opts[:instance_count] and opts[:instance_count].length!=0
222
+ instance_options[:group_ids] = firewalls unless firewalls.empty?
223
+ if opts[:instance_count] and opts[:instance_count].length != 0
224
+ instance_options[:min_count] = opts[:instance_count]
225
+ instance_options[:max_count] = opts[:instance_count]
226
+ end
225
227
  if opts[:snapshot_id] and opts[:device_name]
226
- instance_options.merge!(:block_device_mappings => [{
228
+ instance_options[:block_device_mappings] = [{
227
229
  :snapshot_id => opts[:snapshot_id],
228
230
  :device_name => opts[:device_name]
229
- }])
231
+ }]
230
232
  end
231
233
  safely do
232
234
  new_instance = convert_instance(ec2.launch_instances(image_id, instance_options).first)
@@ -18,6 +18,7 @@
18
18
  require 'deltacloud/base_driver'
19
19
  require 'yaml'
20
20
  require 'deltacloud/drivers/mock/mock_client'
21
+ require 'base64'
21
22
 
22
23
  module Deltacloud::Drivers::Mock
23
24
 
@@ -200,7 +201,7 @@ module Deltacloud::Drivers::Mock
200
201
  :realm_id=>realm_id,
201
202
  :create_image=>true,
202
203
  :actions=>instance_actions_for( 'RUNNING' ),
203
- :user_data => opts[:user_data]
204
+ :user_data => opts[:user_data] ? Base64::decode64(opts[:user_data]) : nil
204
205
  }
205
206
  @client.store(:instances, instance)
206
207
  Instance.new( instance )
@@ -23,10 +23,26 @@ require 'json'
23
23
 
24
24
  module RHEVM
25
25
 
26
+ # NOTE: Injected file will be available in floppy drive inside
27
+ # the instance. (Be sure you 'modprobe floppy' on Linux)
28
+ FILEINJECT_PATH = "deltacloud-user-data.txt"
29
+
26
30
  def self.client(url)
27
31
  RestClient::Resource.new(url)
28
32
  end
29
33
 
34
+ class BackendVersionUnsupportedException < StandardError; end
35
+ class RHEVMBackendException < StandardError
36
+ def initialize(message)
37
+ @message = message
38
+ super
39
+ end
40
+
41
+ def message
42
+ @message
43
+ end
44
+ end
45
+
30
46
  class Client
31
47
 
32
48
  attr_reader :credentials, :api_entrypoint
@@ -61,11 +77,31 @@ module RHEVM
61
77
  RHEVM::client(@api_entrypoint)["/vms/%s" % id].delete(headers)
62
78
  else
63
79
  xml_response = Client::parse_response(RHEVM::client(@api_entrypoint)["/vms/%s/%s" % [id, action]].post('<action/>', headers))
64
- return false if (xml_response/'action/status').first.text!="COMPLETE"
80
+ return false if (xml_response/'action/status').first.text.strip.upcase!="COMPLETE"
65
81
  end
66
82
  return true
67
83
  end
68
84
 
85
+ def api_version?(major)
86
+ headers = {
87
+ :content_type => 'application/xml',
88
+ :accept => 'application/xml'
89
+ }
90
+ headers.merge!(auth_header)
91
+ result_xml = Nokogiri::XML(RHEVM::client(@api_entrypoint)["/"].get(headers))
92
+ (result_xml/'/api/system_version').first[:major].strip == major
93
+ end
94
+
95
+ def cluster_version?(cluster_id, major)
96
+ headers = {
97
+ :content_type => 'application/xml',
98
+ :accept => 'application/xml'
99
+ }
100
+ headers.merge!(auth_header)
101
+ result_xml = Nokogiri::XML(RHEVM::client(@api_entrypoint)["/clusters/%s" % cluster_id].get(headers))
102
+ (result_xml/'/cluster/version').first[:major].strip == major
103
+ end
104
+
69
105
  def create_vm(template_id, opts={})
70
106
  opts ||= {}
71
107
  builder = Nokogiri::XML::Builder.new do
@@ -78,6 +114,23 @@ module RHEVM
78
114
  cpu {
79
115
  topology( :cores => (opts[:hwp_cpu] || '1'), :sockets => '1' )
80
116
  }
117
+ if opts[:user_data] and not opts[:user_data].empty?
118
+ if api_version?('3') and cluster_version?((opts[:realm_id] || clusters.first.id), '3')
119
+ custom_properties {
120
+ #
121
+ # FIXME: 'regexp' parameter is just a temporary workaround. This
122
+ # is a reported and verified bug and should be fixed in next
123
+ # RHEV-M release.
124
+ #
125
+ custom_property({
126
+ :name => "floppyinject",
127
+ :value => "#{RHEVM::FILEINJECT_PATH}:#{Base64.decode64(opts[:user_data])}",
128
+ :regexp => "^([^:]+):(.*)$"})
129
+ }
130
+ else
131
+ raise BackendVersionUnsupportedException.new
132
+ end
133
+ end
81
134
  }
82
135
  end
83
136
  headers = opts[:headers] || {}
@@ -86,7 +139,16 @@ module RHEVM
86
139
  :accept => 'application/xml',
87
140
  })
88
141
  headers.merge!(auth_header)
89
- vm = RHEVM::client(@api_entrypoint)["/vms"].post(Nokogiri::XML(builder.to_xml).root.to_s, headers)
142
+ begin
143
+ vm = RHEVM::client(@api_entrypoint)["/vms"].post(Nokogiri::XML(builder.to_xml).root.to_s, headers)
144
+ rescue
145
+ if $!.respond_to?(:http_body)
146
+ fault = (Nokogiri::XML($!.http_body)/'/fault/detail').first
147
+ fault = fault.text.gsub(/\[|\]/, '') if fault
148
+ end
149
+ fault ||= $!.message
150
+ raise RHEVMBackendException::new(fault)
151
+ end
90
152
  RHEVM::VM::new(self, Nokogiri::XML(vm).root)
91
153
  end
92
154
 
@@ -37,6 +37,8 @@ class RHEVMDriver < Deltacloud::BaseDriver
37
37
  constraint :max_length, 50
38
38
  end
39
39
 
40
+ feature :instances, :user_data
41
+
40
42
  USER_NAME_MAX = feature(:instances, :user_name).constraints[:max_length]
41
43
 
42
44
  # FIXME: These values are just for ilustration
@@ -192,6 +194,9 @@ class RHEVMDriver < Deltacloud::BaseDriver
192
194
  params[:hwp_id] = opts[:hwp_id] if opts[:hwp_id]
193
195
  params[:hwp_memory] = opts[:hwp_memory] if opts[:hwp_memory]
194
196
  params[:hwp_cpu] = opts[:hwp_cpu] if opts[:hwp_cpu]
197
+ if opts[:user_data]
198
+ params[:user_data] = opts[:user_data].gsub(/\n/,'')
199
+ end
195
200
  convert_instance(client, client.create_vm(image_id, params))
196
201
  end
197
202
  end
@@ -305,8 +310,8 @@ class RHEVMDriver < Deltacloud::BaseDriver
305
310
  def convert_realm(r, dc)
306
311
  Realm.new(
307
312
  :id => r.id,
308
- :name => dc.name,
309
- :state => dc.status == 'UP' ? 'AVAILABLE' : 'DOWN',
313
+ :name => r.name,
314
+ :state => dc.status.strip.upcase == 'UP' ? 'AVAILABLE' : 'DOWN',
310
315
  :limit => :unlimited
311
316
  )
312
317
  end
@@ -41,14 +41,14 @@ module Deltacloud::Drivers::VSphere
41
41
  vsphere = new_client(credentials)
42
42
  safely do
43
43
  service = vsphere.serviceInstance.content
44
- max_memory, max_cpu_cores = 0, 0
44
+ max_memory, max_cpu_cores = [], []
45
45
  service.rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).each do |dc|
46
- max_memory += dc.hostFolder.childEntity.first.summary.effectiveMemory
47
- max_cpu_cores += dc.hostFolder.childEntity.first.summary.numCpuCores
46
+ max_memory << dc.hostFolder.childEntity.first.summary.effectiveMemory
47
+ max_cpu_cores << dc.hostFolder.childEntity.first.summary.numCpuCores
48
48
  end
49
49
  [Deltacloud::HardwareProfile::new('default') do
50
- cpu (1..max_cpu_cores)
51
- memory (128..max_memory)
50
+ cpu (1..max_cpu_cores.min)
51
+ memory (128..max_memory.min)
52
52
  architecture ['x86_64', 'i386']
53
53
  end]
54
54
  end
@@ -207,6 +207,7 @@ module Deltacloud::Drivers::VSphere
207
207
  safely do
208
208
  rootFolder = vsphere.serviceInstance.content.rootFolder
209
209
  vm = find_vm(credentials, opts[:image_id])
210
+ raise "ERROR: Could not find the image in given datacenter" unless vm[:instance]
210
211
  # New instance need valid resource pool and datastore to be placed.
211
212
  # For this reason, realm_id **needs** to be set.
212
213
  if opts and opts[:realm_id]
@@ -219,6 +220,7 @@ module Deltacloud::Drivers::VSphere
219
220
  relocate = { :pool => resourcePool, :datastore => datastore }
220
221
  relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec(relocate)
221
222
  # Set extra configuration for VM, like template_id
223
+ raise "ERROR: Memory must be multiple of 4" unless valid_memory_value?(opts[:hwp_memory])
222
224
  machine_config = {
223
225
  :memoryMB => opts[:hwp_memory],
224
226
  :numCPUs => opts[:hwp_cpu],
@@ -240,6 +242,7 @@ module Deltacloud::Drivers::VSphere
240
242
  machine_config[:extraConfig] << {
241
243
  :key => 'user_data_file', :value => "#{opts[:name]}.iso"
242
244
  }
245
+ device.connectable.startConnected = true
243
246
  device.backing = RbVmomi::VIM.VirtualCdromIsoBackingInfo(:fileName => "[#{opts[:realm_id] || vm[:datastore]}] "+
244
247
  "/#{VSphere::FileManager::DIRECTORY_PATH}/#{opts[:name]}.iso")
245
248
  machine_config.merge!({
@@ -259,6 +262,7 @@ module Deltacloud::Drivers::VSphere
259
262
  machine_config[:extraConfig] << {
260
263
  :key => 'user_iso_file', :value => "#{opts[:name]}.iso"
261
264
  }
265
+ device.connectable.startConnected = true
262
266
  device.backing = RbVmomi::VIM.VirtualCdromIsoBackingInfo(:fileName => "[#{opts[:realm_id] || vm[:datastore]}] "+
263
267
  "/#{VSphere::FileManager::DIRECTORY_PATH}/#{opts[:name]}.iso")
264
268
  machine_config.merge!({
@@ -376,7 +380,7 @@ module Deltacloud::Drivers::VSphere
376
380
  def convert_realm(datastore)
377
381
  Realm::new(
378
382
  :id => datastore.name,
379
- :name => datastore.name,
383
+ :name => datastore.name,
380
384
  :limit => datastore.summary.freeSpace,
381
385
  :state => datastore.summary.accessible ? 'AVAILABLE' : 'UNAVAILABLE'
382
386
  )
@@ -400,6 +404,10 @@ module Deltacloud::Drivers::VSphere
400
404
  new_state
401
405
  end
402
406
 
407
+ def valid_memory_value?(val)
408
+ true if (val.to_i%4) == 0
409
+ end
410
+
403
411
  end
404
412
 
405
413
  end