wakame-vdc-dcmgr 10.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/LICENSE +202 -0
  2. data/NOTICE +1 -0
  3. data/Rakefile +142 -0
  4. data/bin/collector +46 -0
  5. data/config/dcmgr.conf.example +9 -0
  6. data/config/initializers/isono.rb +43 -0
  7. data/config/initializers/passenger.rb +6 -0
  8. data/config/initializers/sequel.rb +21 -0
  9. data/config/path_resolver.rb +12 -0
  10. data/lib/dcmgr.rb +115 -0
  11. data/lib/dcmgr/endpoints/core_api.rb +1004 -0
  12. data/lib/dcmgr/endpoints/core_api_mock.rb +816 -0
  13. data/lib/dcmgr/endpoints/errors.rb +55 -0
  14. data/lib/dcmgr/endpoints/metadata.rb +129 -0
  15. data/lib/dcmgr/logger.rb +44 -0
  16. data/lib/dcmgr/models/account.rb +104 -0
  17. data/lib/dcmgr/models/account_resource.rb +16 -0
  18. data/lib/dcmgr/models/base.rb +69 -0
  19. data/lib/dcmgr/models/base_new.rb +371 -0
  20. data/lib/dcmgr/models/frontend_system.rb +38 -0
  21. data/lib/dcmgr/models/host_pool.rb +102 -0
  22. data/lib/dcmgr/models/image.rb +46 -0
  23. data/lib/dcmgr/models/instance.rb +255 -0
  24. data/lib/dcmgr/models/instance_netfilter_group.rb +16 -0
  25. data/lib/dcmgr/models/instance_nic.rb +68 -0
  26. data/lib/dcmgr/models/instance_spec.rb +21 -0
  27. data/lib/dcmgr/models/ip_lease.rb +42 -0
  28. data/lib/dcmgr/models/netfilter_group.rb +88 -0
  29. data/lib/dcmgr/models/netfilter_rule.rb +21 -0
  30. data/lib/dcmgr/models/network.rb +32 -0
  31. data/lib/dcmgr/models/physical_host.rb +67 -0
  32. data/lib/dcmgr/models/request_log.rb +25 -0
  33. data/lib/dcmgr/models/ssh_key_pair.rb +55 -0
  34. data/lib/dcmgr/models/storage_pool.rb +134 -0
  35. data/lib/dcmgr/models/tag.rb +126 -0
  36. data/lib/dcmgr/models/tag_mapping.rb +28 -0
  37. data/lib/dcmgr/models/volume.rb +130 -0
  38. data/lib/dcmgr/models/volume_snapshot.rb +47 -0
  39. data/lib/dcmgr/node_modules/hva_collector.rb +134 -0
  40. data/lib/dcmgr/node_modules/sta_collector.rb +72 -0
  41. data/lib/dcmgr/scheduler.rb +12 -0
  42. data/lib/dcmgr/scheduler/find_last.rb +16 -0
  43. data/lib/dcmgr/scheduler/find_random.rb +16 -0
  44. data/lib/dcmgr/stm/instance.rb +25 -0
  45. data/lib/dcmgr/stm/snapshot_context.rb +33 -0
  46. data/lib/dcmgr/stm/volume_context.rb +65 -0
  47. data/lib/dcmgr/web/base.rb +21 -0
  48. data/lib/sinatra/accept_media_types.rb +128 -0
  49. data/lib/sinatra/lazy_auth.rb +56 -0
  50. data/lib/sinatra/rabbit.rb +278 -0
  51. data/lib/sinatra/respond_to.rb +272 -0
  52. data/lib/sinatra/sequel_transaction.rb +27 -0
  53. data/lib/sinatra/static_assets.rb +83 -0
  54. data/lib/sinatra/url_for.rb +44 -0
  55. data/web/api/config.ru +20 -0
  56. data/web/api/public/index.html +0 -0
  57. data/web/metadata/config.ru +20 -0
  58. data/web/metadata/public/index.html +0 -0
  59. metadata +326 -0
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ if defined?(PhusionPassenger)
4
+ # redirect STDOUT messages to STDERR
5
+ $> = STDERR
6
+ end
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'sequel'
4
+ db = Sequel.connect(Dcmgr.conf.database_url)
5
+ #require 'logger'
6
+ #db.loggers << Logger.new(STDOUT)
7
+ if db.is_a?(Sequel::MySQL::Database)
8
+ Sequel::MySQL.default_charset = 'utf8'
9
+ Sequel::MySQL.default_collate = 'utf8_general_ci'
10
+ Sequel::MySQL.default_engine = 'InnoDB'
11
+
12
+ db << "SET AUTOCOMMIT=0"
13
+ end
14
+
15
+ # Disable TEXT to Sequel::SQL::Blob translation.
16
+ # see the thread: MySQL text turning into blobs
17
+ # http://groups.google.com/group/sequel-talk/browse_thread/thread/d0f4c85abe9b3227/9ceaf291f90111e6
18
+ # lib/sequel/adapters/mysql.rb
19
+ [249, 250, 251, 252].each { |v|
20
+ Sequel::MySQL::MYSQL_TYPES.delete(v)
21
+ }
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ if Object.const_defined?(:Gem)
6
+ begin
7
+ gem 'isono'
8
+ rescue Gem::LoadError => e
9
+ end
10
+ end
11
+ require 'isono'
12
+ require 'dcmgr'
data/lib/dcmgr.rb ADDED
@@ -0,0 +1,115 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Dcmgr
4
+ VERSION='10.11.0'
5
+
6
+ class << self
7
+ def conf
8
+ @conf
9
+ end
10
+
11
+ def configure(config_path=nil, &blk)
12
+ return self if @conf
13
+
14
+ if config_path.is_a?(String)
15
+ raise "Could not find configration file: #{config_path}" unless File.exists?(config_path)
16
+
17
+ require 'configuration'
18
+ code= <<-__END
19
+ Configuration('global') do
20
+ #{File.read(config_path)}
21
+ end
22
+ __END
23
+ @conf = eval(code)
24
+ else
25
+ @conf = Configuration.for('global', &blk)
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ def run_initializers()
32
+ raise "Complete the configuration prior to run_initializers()." if @conf.nil?
33
+ initializer_hooks.each { |n|
34
+ n.call
35
+ }
36
+ end
37
+
38
+ def initializer_hooks(&blk)
39
+ @initializer_hooks ||= []
40
+ if blk
41
+ @initializer_hooks << blk
42
+ end
43
+ @initializer_hooks
44
+ end
45
+
46
+ end
47
+
48
+ initializer_hooks {
49
+ Dcmgr.class_eval {
50
+ DCMGR_ROOT = ENV['DCMGR_ROOT'] || File.expand_path('../../', __FILE__)
51
+ }
52
+ }
53
+
54
+ # Add conf/initializers/*.rb loader
55
+ initializer_hooks {
56
+ initializers_root = File.expand_path('config/initializers', DCMGR_ROOT)
57
+
58
+ if File.directory?(initializers_root)
59
+ Dir.glob("#{initializers_root}/*.rb") { |f|
60
+ ::Kernel.load(f)
61
+ }
62
+ end
63
+ }
64
+
65
+ autoload :Logger, 'dcmgr/logger'
66
+
67
+ module Models
68
+ autoload :Base, 'dcmgr/models/base'
69
+
70
+ CREATE_TABLE_CLASSES=[:Account,:Tag,:TagMapping,:FrontendSystem,
71
+ :Image,:HostPool,:RequestLog,:Instance,
72
+ :NetfilterGroup, :NetfilterRule,
73
+ :StoragePool,:Volume,:VolumeSnapshot,
74
+ :InstanceNetfilterGroup,
75
+ :InstanceSpec, :InstanceNic, :Network, :IpLease,
76
+ :SshKeyPair].freeze
77
+ autoload :BaseNew, 'dcmgr/models/base_new'
78
+ autoload :Account, 'dcmgr/models/account'
79
+ autoload :Tag, 'dcmgr/models/tag'
80
+ autoload :TagMapping, 'dcmgr/models/tag_mapping'
81
+ autoload :AccountResource, 'dcmgr/models/account_resource'
82
+ autoload :Instance, 'dcmgr/models/instance'
83
+ autoload :Image, 'dcmgr/models/image'
84
+ autoload :HostPool, 'dcmgr/models/host_pool'
85
+ autoload :RequestLog, 'dcmgr/models/request_log'
86
+ autoload :FrontendSystem, 'dcmgr/models/frontend_system'
87
+ autoload :StoragePool, 'dcmgr/models/storage_pool'
88
+ autoload :Volume, 'dcmgr/models/volume'
89
+ autoload :VolumeSnapshot, 'dcmgr/models/volume_snapshot'
90
+ autoload :NetfilterGroup, 'dcmgr/models/netfilter_group'
91
+ autoload :NetfilterRule, 'dcmgr/models/netfilter_rule'
92
+ autoload :InstanceSpec, 'dcmgr/models/instance_spec'
93
+ autoload :InstanceNic, 'dcmgr/models/instance_nic'
94
+ autoload :Network, 'dcmgr/models/network'
95
+ autoload :IpLease, 'dcmgr/models/ip_lease'
96
+ autoload :InstanceNetfilterGroup, 'dcmgr/models/instance_netfilter_group'
97
+ autoload :SshKeyPair, 'dcmgr/models/ssh_key_pair'
98
+ end
99
+
100
+ module Endpoints
101
+ autoload :CoreAPI, 'dcmgr/endpoints/core_api'
102
+ autoload :Metadata, 'dcmgr/endpoints/metadata'
103
+ end
104
+
105
+ module NodeModules
106
+ autoload :StaCollector, 'dcmgr/node_modules/sta_collector'
107
+ autoload :HvaCollector, 'dcmgr/node_modules/hva_collector'
108
+ end
109
+
110
+ module Stm
111
+ autoload :VolumeContext, 'dcmgr/stm/volume_context'
112
+ autoload :SnapshotContext, 'dcmgr/stm/snapshot_context'
113
+ autoload :Instance, 'dcmgr/stm/instance'
114
+ end
115
+ end
@@ -0,0 +1,1004 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'sinatra/base'
4
+ require 'sinatra/rabbit'
5
+ require 'sinatra/sequel_transaction'
6
+
7
+ require 'json'
8
+ require 'extlib/hash'
9
+
10
+ require 'dcmgr/endpoints/errors'
11
+
12
+ module Dcmgr
13
+ module Endpoints
14
+ class CoreAPI < Sinatra::Base
15
+ include Dcmgr::Logger
16
+ register Sinatra::Rabbit
17
+ register Sinatra::SequelTransaction
18
+
19
+ disable :sessions
20
+ disable :show_exceptions
21
+
22
+ before do
23
+ request.env['dcmgr.frotend_system.id'] = 1
24
+ request.env['HTTP_X_VDC_REQUESTER_TOKEN']='u-xxxxxx'
25
+ request.env['HTTP_X_VDC_ACCOUNT_UUID']='a-00000000'
26
+ end
27
+
28
+ before do
29
+ @params = parsed_request_body if request.post?
30
+ @account = Models::Account[request.env['HTTP_X_VDC_ACCOUNT_UUID']]
31
+ @requester_token = request.env['HTTP_X_VDC_REQUESTER_TOKEN']
32
+ #@frontend = Models::FrontendSystem[request.env['dcmgr.frotend_system.id']]
33
+
34
+ #raise InvalidRequestCredentials if !(@account && @frontend)
35
+ raise DisabledAccount if @account.disable?
36
+ end
37
+
38
+ before do
39
+ Thread.current[Dcmgr::Models::BaseNew::LOCK_TABLES_KEY] = {}
40
+ end
41
+
42
+ def find_by_uuid(model_class, uuid)
43
+ if model_class.is_a?(Symbol)
44
+ model_class = Models.const_get(model_class)
45
+ end
46
+ model_class[uuid] || raise(UnknownUUIDResource, uuid.to_s)
47
+ end
48
+
49
+ def find_account(account_uuid)
50
+ find_by_uuid(:Account, account_uuid)
51
+ end
52
+
53
+ # Returns deserialized hash from HTTP body. Serialization fromat
54
+ # is guessed from content type header. The query string params
55
+ # is returned if none of content type header is in HTTP headers.
56
+ # This method is called only when the request method is POST.
57
+ def parsed_request_body
58
+ # @mime_types should be defined by sinatra/respond_to.rb plugin.
59
+ if @mime_types.nil?
60
+ # use query string as requested params if Content-Type
61
+ # header was not sent.
62
+ # ActiveResource library tells the one level nested hash which has
63
+ # {'something key'=>real_params} so that dummy key is assinged here.
64
+ hash = {:dummy=>@params}
65
+ else
66
+ mime = @mime_types.first
67
+ case mime.to_s
68
+ when 'application/json', 'text/json'
69
+ require 'json'
70
+ hash = JSON.load(request.body)
71
+ hash = hash.to_mash
72
+ when 'application/yaml', 'text/yaml'
73
+ require 'yaml'
74
+ hash = YAML.load(request.body)
75
+ hash = hash.to_mash
76
+ else
77
+ raise "Unsupported body document type: #{mime.to_s}"
78
+ end
79
+ end
80
+ return hash.values.first
81
+ end
82
+
83
+ def response_to(res)
84
+ mime = @mime_types.first unless @mime_types.nil?
85
+ case mime.to_s
86
+ when 'application/yaml', 'text/yaml'
87
+ content_type 'yaml'
88
+ res.to_yaml
89
+ when 'application/xml', 'text/xml'
90
+ raise NotImplementedError
91
+ else
92
+ content_type 'json'
93
+ res.to_json
94
+ end
95
+ end
96
+
97
+ # I am not going to use error(ex, &blk) hook since it works only
98
+ # when matches the Exception class exactly. I expect to match
99
+ # whole subclasses of APIError so that override handle_exception!().
100
+ def handle_exception!(boom)
101
+ logger.error(boom)
102
+ if boom.kind_of?(APIError)
103
+ @env['sinatra.error'] = boom
104
+ error(boom.status_code, boom.class.to_s)
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def create_volume_from_snapshot(account_id, snapshot_id)
111
+ vs = find_by_uuid(:VolumeSnapshot, snapshot_id)
112
+ raise UnknownVolumeSnapshot if vs.nil?
113
+ vs.create_volume(account_id)
114
+ end
115
+
116
+ def examine_owner(account_resource)
117
+ if @account.canonical_uuid == account_resource.account_id ||
118
+ @account.canonical_uuid == 'a-00000000'
119
+ return true
120
+ else
121
+ return false
122
+ end
123
+ end
124
+
125
+ collection :accounts do
126
+ operation :index do
127
+ control do
128
+ end
129
+ end
130
+
131
+ operation :show do
132
+ control do
133
+ a = find_account(params[:id])
134
+ respond_to { |f|
135
+ f.json { a.to_hash_document.to_json }
136
+ }
137
+ end
138
+ end
139
+
140
+ operation :create do
141
+ description 'Register a new account'
142
+ control do
143
+ a = Models::Account.create()
144
+ respond_to { |f|
145
+ f.json { a.to_hash_document.to_json }
146
+ }
147
+ end
148
+ end
149
+
150
+ operation :destroy do
151
+ description 'Unregister the account.'
152
+ # Associated resources all have to be destroied prior to
153
+ # removing the account.
154
+ #param :id, :string, :required
155
+ control do
156
+ a = find_account(params[:id])
157
+ a.destroy
158
+
159
+ respond_to { |f|
160
+ f.json { {} }
161
+ }
162
+ end
163
+ end
164
+
165
+ operation :enable, :method=>:get, :member=>true do
166
+ description 'Enable the account for all operations'
167
+ control do
168
+ a = find_account(params[:id])
169
+ a.enabled = Models::Account::ENABLED
170
+ a.save
171
+
172
+ respond_to { |f|
173
+ f.json { {} }
174
+ }
175
+ end
176
+ end
177
+
178
+ operation :disable, :method=>:get, :member=>true do
179
+ description 'Disable the account for all operations'
180
+ control do
181
+ a = find_account(params[:id])
182
+ a.enabled = Models::Account::DISABLED
183
+ a.save
184
+
185
+ respond_to { |f|
186
+ f.json { {} }
187
+ }
188
+ end
189
+ end
190
+
191
+ operation :add_tag, :method=>:get, :member=>true do
192
+ description 'Add a tag belongs to the account'
193
+ #param :tag_name, :string, :required
194
+ control do
195
+ a = find_account(params[:id])
196
+
197
+ tag_class = Models::Tags.find_tag_class(params[:tag_name])
198
+ raise "UnknownTagClass: #{params[:tag_name]}" if tag_class.nil?
199
+
200
+ a.add_tag(tag_class.new(:name=>params[:name]))
201
+ end
202
+ end
203
+
204
+ operation :remove_tag, :method=>:get, :member=>true do
205
+ description 'Unlink the associated tag of the account'
206
+ #param :tag_id, :string, :required
207
+ control do
208
+ a = find_account(params[:id])
209
+ t = a.tags_dataset.filter(:uuid=>params[:tag_id]).first
210
+ if t
211
+ a.remove_tag(t)
212
+ else
213
+ raise "Unknown or disassociated tag for #{a.cuuid}: #{params[:tag_id]}"
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ collection :tags do
220
+ operation :create do
221
+ description 'Register new tag to the account'
222
+ #param :tag_name, :string, :required
223
+ #param :type_id, :fixnum, :optional
224
+ #param :account_id, :string, :optional
225
+ control do
226
+ tag_class = Models::Tag.find_tag_class(params[:tag_name])
227
+
228
+ tag_class.create
229
+
230
+ end
231
+ end
232
+
233
+ operation :show do
234
+ #param :account_id, :string, :optional
235
+ control do
236
+ end
237
+ end
238
+
239
+ operation :destroy do
240
+ description 'Create a new user'
241
+ control do
242
+ end
243
+ end
244
+
245
+ operation :update do
246
+ control do
247
+ end
248
+ end
249
+ end
250
+
251
+ # Endpoint to handle VM instance.
252
+ collection :instances do
253
+ operation :index do
254
+ description 'Show list of instances'
255
+ # params start, fixnum, optional
256
+ # params limit, fixnum, optional
257
+ control do
258
+ start = params[:start].to_i
259
+ start = start < 1 ? 0 : start
260
+ limit = params[:limit].to_i
261
+ limit = limit < 1 ? nil : limit
262
+
263
+ total_ds = Models::Instance.where(:account_id=>@account.canonical_uuid)
264
+ partial_ds = total_ds.dup.order(:id)
265
+ partial_ds = partial_ds.limit(limit, start) if limit.is_a?(Integer)
266
+
267
+ res = [{
268
+ :owner_total => total_ds.count,
269
+ :start => start,
270
+ :limit => limit,
271
+ :results=> partial_ds.all.map {|i| i.to_api_document }
272
+ }]
273
+
274
+ respond_to { |f|
275
+ f.json {res.to_json}
276
+ }
277
+ end
278
+ end
279
+
280
+ operation :create do
281
+ description 'Runs a new VM instance'
282
+ # param :image_id, string, :required
283
+ # param :instance_spec_id, string, :required
284
+ # param :host_pool_id, string, :optional
285
+ # param :host_name, string, :optional
286
+ # param :user_data, string, :optional
287
+ # param :nf_group, array, :optional
288
+ # param :ssh_key, string, :optional
289
+ control do
290
+ Models::Instance.lock!
291
+
292
+ wmi = find_by_uuid(:Image, params[:image_id])
293
+ spec = find_by_uuid(:InstanceSpec, (params[:instance_spec_id] || 'is-kpf0pasc'))
294
+
295
+ if params[:host_pool_id]
296
+ hp = Models::HostPool[params[:host_pool_id]]
297
+ raise OutOfHostCapacity unless hp.check_capacity(spec)
298
+ else
299
+ # TODO: schedule a host pool owned by SharedPool account.
300
+ end
301
+
302
+ raise UnknownHostPool, "Could not find host pool: #{params[:host_pool_id]}" if hp.nil?
303
+
304
+ inst = hp.create_instance(@account, wmi, spec) do |i|
305
+ # TODO: do not use rand() to decide vnc port.
306
+ i.runtime_config = {:vnc_port=>rand(2000), :telnet_port=> (rand(2000) + 2000)}
307
+ i.user_data = params[:user_data] || ''
308
+
309
+ if params[:ssh_key]
310
+ ssh_key_pair = Models::SshKeyPair.find(:account_id=>@account.canonical_uuid,
311
+ :name=>params[:ssh_key])
312
+ if ssh_key_pair.nil?
313
+ raise UnknownSshKeyPair, "#{params[:ssh_key]}"
314
+ else
315
+ i.ssh_key_pair_id = ssh_key_pair.canonical_uuid
316
+ end
317
+ end
318
+ end
319
+
320
+ unless params[:nf_group].is_a?(Array)
321
+ params[:nf_group] = ['default']
322
+ end
323
+ inst.join_nfgroup_by_name(@account.canonical_uuid, params[:nf_group])
324
+
325
+ case wmi.boot_dev_type
326
+ when Models::Image::BOOT_DEV_SAN
327
+ # create new volume from snapshot.
328
+ snapshot_id = wmi.source[:snapshot_id]
329
+ vol = create_volume_from_snapshot(@account.canonical_uuid, snapshot_id)
330
+
331
+ vol.instance = inst
332
+ vol.save
333
+ res = Dcmgr.messaging.submit("kvm-handle.#{hp.node_id}", 'run_vol_store', inst.canonical_uuid, vol.canonical_uuid)
334
+ when Models::Image::BOOT_DEV_LOCAL
335
+ res = Dcmgr.messaging.submit("kvm-handle.#{hp.node_id}", 'run_local_store', inst.canonical_uuid)
336
+ else
337
+ raise "Unknown boot type"
338
+ end
339
+ respond_to { |f|
340
+ f.json { inst.to_api_document.to_json }
341
+ }
342
+ end
343
+ end
344
+
345
+ operation :show do
346
+ #param :account_id, :string, :optional
347
+ control do
348
+ i = find_by_uuid(:Instance, params[:id])
349
+ raise UnknownInstance if i.nil?
350
+
351
+ respond_to { |f|
352
+ f.json { i.to_api_document.to_json }
353
+ }
354
+ end
355
+ end
356
+
357
+ operation :destroy do
358
+ description 'Shutdown the instance'
359
+ control do
360
+ Models::Instance.lock!
361
+ i = find_by_uuid(:Instance, params[:id])
362
+ if examine_owner(i)
363
+ else
364
+ raise OperationNotPermitted
365
+ end
366
+ res = Dcmgr.messaging.submit("kvm-handle.#{i.host_pool.node_id}", 'terminate', i.canonical_uuid)
367
+ respond_to { |f|
368
+ f.json { i.canonical_uuid }
369
+ }
370
+ end
371
+ end
372
+
373
+ operation :reboot, :method=>:put, :member=>true do
374
+ description 'Reboots the instance'
375
+ control do
376
+ Models::Instance.lock!
377
+ i = find_by_uuid(:Instance, params[:id])
378
+ end
379
+ end
380
+ end
381
+
382
+ collection :images do
383
+ operation :create do
384
+ description 'Register new machine image'
385
+ control do
386
+ Models::Image.lock!
387
+ raise NotImplementedError
388
+ end
389
+ end
390
+
391
+ operation :index do
392
+ description 'Show list of machine images'
393
+ control do
394
+ start = params[:start].to_i
395
+ start = start < 1 ? 0 : start
396
+ limit = params[:limit].to_i
397
+ limit = limit < 1 ? nil : limit
398
+
399
+ total_ds = Models::Image.where(:account_id=>@account.canonical_uuid)
400
+ partial_ds = total_ds.dup.order(:id)
401
+ partial_ds = partial_ds.limit(limit, start) if limit.is_a?(Integer)
402
+
403
+ res = [{
404
+ :owner_total => total_ds.count,
405
+ :start => start,
406
+ :limit => limit,
407
+ :results=> partial_ds.all.map {|i| i.to_hash }
408
+ }]
409
+
410
+ respond_to { |f|
411
+ f.json {res.to_json}
412
+ }
413
+ end
414
+ end
415
+
416
+ operation :show do
417
+ description "Show a machine image details."
418
+ control do
419
+ i = find_by_uuid(:Image, params[:id])
420
+ # TODO: add visibility by account check
421
+ unless examine_owner(i)
422
+ raise OperationNotPermitted
423
+ end
424
+ respond_to { |f|
425
+ f.json { i.to_hash.to_json }
426
+ }
427
+ end
428
+ end
429
+
430
+ operation :destroy do
431
+ description 'Delete a machine image'
432
+ control do
433
+ Models::Image.lock!
434
+ i = find_by_uuid(:Image, params[:id])
435
+ if examine_owner(i)
436
+ i.delete
437
+ else
438
+ raise OperationNotPermitted
439
+ end
440
+ end
441
+ end
442
+ end
443
+
444
+ collection :host_pools do
445
+ operation :index do
446
+ description 'Show list of host pools'
447
+ control do
448
+ start = params[:start].to_i
449
+ start = start < 1 ? 0 : start
450
+ limit = params[:limit].to_i
451
+ limit = limit < 1 ? nil : limit
452
+
453
+ total_ds = Models::HostPool.where(:account_id=>@account.canonical_uuid)
454
+ partial_ds = total_ds.dup.order(:id)
455
+ partial_ds = partial_ds.limit(limit, start) if limit.is_a?(Integer)
456
+
457
+ res = [{
458
+ :owner_total => total_ds.count,
459
+ :start => start,
460
+ :limit => limit,
461
+ :results=> partial_ds.all.map {|i| i.to_hash }
462
+ }]
463
+
464
+ respond_to { |f|
465
+ f.json {res.to_json}
466
+ }
467
+ end
468
+ end
469
+
470
+ operation :show do
471
+ description 'Show status of the host'
472
+ #param :account_id, :string, :optional
473
+ control do
474
+ hp = find_by_uuid(:HostPool, params[:id])
475
+ raise OperationNotPermitted unless examine_owner(hp)
476
+
477
+ respond_to { |f|
478
+ f.json { hp.to_hash.to_json }
479
+ }
480
+ end
481
+ end
482
+ end
483
+
484
+ collection :volumes do
485
+ operation :index do
486
+ description 'Show lists of the volume'
487
+ # params start, fixnum, optional
488
+ # params limit, fixnum, optional
489
+ control do
490
+ start = params[:start].to_i
491
+ start = start < 1 ? 0 : start
492
+ limit = params[:limit].to_i
493
+ limit = limit < 1 ? nil : limit
494
+
495
+ total_v = Models::Volume.where(:account_id => @account.canonical_uuid)
496
+ partial_v = total_v.dup.order(:id)
497
+ partial_v = partial_v.limit(limit, start) if limit.is_a?(Integer)
498
+ res = [{
499
+ :owner_total => total_v.count,
500
+ :start => start,
501
+ :limit => limit,
502
+ :results => partial_v.all.map { |v| v.to_hash_document}
503
+ }]
504
+ respond_to { |f|
505
+ f.json { res.to_json}
506
+ }
507
+ end
508
+ end
509
+
510
+ operation :show do
511
+ description 'Show the volume status'
512
+ # params id, string, required
513
+ control do
514
+ volume_id = params[:id]
515
+ raise UndefinedVolumeID if volume_id.nil?
516
+ v = find_by_uuid(:Volume, volume_id)
517
+ respond_to { |f|
518
+ f.json { v.to_hash_document.to_json}
519
+ }
520
+ end
521
+ end
522
+
523
+ operation :create do
524
+ description 'Create the new volume'
525
+ # params volume_size, string, required
526
+ # params snapshot_id, string, optional
527
+ # params storage_pool_id, string, optional
528
+ control do
529
+ Models::Volume.lock!
530
+ if params[:snapshot_id]
531
+ v = create_volume_from_snapshot(@account.canonical_uuid, params[:snapshot_id])
532
+ sp = v.storage_pool
533
+ elsif params[:volume_size]
534
+ raise InvalidVolumeSize if !(Dcmgr.conf.create_volume_max_size.to_i >= params[:volume_size].to_i) || !(params[:volume_size\
535
+ ].to_i >= Dcmgr.conf.create_volume_min_size.to_i)
536
+ if params[:storage_pool_id]
537
+ sp = find_by_uuid(:StoragePool, params[:storage_pool_id])
538
+ raise StoragePoolNotPermitted if sp.account_id != @account.canonical_uuid
539
+ end
540
+ raise UnknownStoragePool if sp.nil?
541
+ begin
542
+ v = sp.create_volume(@account.canonical_uuid, params[:volume_size])
543
+ rescue Models::Volume::DiskError => e
544
+ logger.error(e)
545
+ raise OutOfDiskSpace
546
+ rescue Sequel::DatabaseError => e
547
+ logger.error(e)
548
+ raise DatabaseError
549
+ end
550
+ else
551
+ raise UndefinedRequiredParameter
552
+ end
553
+
554
+ res = Dcmgr.messaging.submit("zfs-handle.#{sp.values[:node_id]}", 'create_volume', v.canonical_uuid)
555
+ respond_to { |f|
556
+ f.json { v.to_hash_document.to_json}
557
+ }
558
+ end
559
+ end
560
+
561
+ operation :destroy do
562
+ description 'Delete the volume'
563
+ # params id, string, required
564
+ control do
565
+ Models::Volume.lock!
566
+ volume_id = params[:id]
567
+ raise UndefinedVolumeID if volume_id.nil?
568
+
569
+ begin
570
+ v = Models::Volume.delete_volume(@account.canonical_uuid, volume_id)
571
+ rescue Models::Volume::RequestError => e
572
+ logger.error(e)
573
+ raise InvalidDeleteRequest
574
+ end
575
+ raise UnknownVolume if v.nil?
576
+ sp = v.storage_pool
577
+
578
+ res = Dcmgr.messaging.submit("zfs-handle.#{sp.values[:node_id]}", 'delete_volume', v.canonical_uuid)
579
+ respond_to { |f|
580
+ f.json { v.to_hash_document.to_json}
581
+ }
582
+ end
583
+ end
584
+
585
+ operation :attach, :method =>:put, :member =>true do
586
+ description 'Attachd the volume'
587
+ # params id, string, required
588
+ # params instance_id, string, required
589
+ control do
590
+ raise UndefinedInstanceID if params[:instance_id].nil?
591
+ raise UndefinedVolumeID if params[:id].nil?
592
+
593
+ i = find_by_uuid(:Instance, params[:instance_id])
594
+ raise UnknownInstance if i.nil?
595
+
596
+ v = find_by_uuid(:Volume, params[:id])
597
+ raise UnknownVolume if v.nil?
598
+
599
+ v.instance = i
600
+ v.save
601
+ res = Dcmgr.messaging.submit("kvm-handle.#{i.host_pool.node_id}", 'attach', i.canonical_uuid, v.canonical_uuid)
602
+
603
+ respond_to { |f|
604
+ f.json { v.to_hash_document.to_json}
605
+ }
606
+ end
607
+ end
608
+
609
+ operation :detach, :method =>:put, :member =>true do
610
+ description 'Detachd the volume'
611
+ # params id, string, required
612
+ control do
613
+ raise UndefinedVolumeID if params[:id].nil?
614
+
615
+ v = find_by_uuid(:Volume, params[:id])
616
+ raise UnknownVolume if v.nil?
617
+ i = v.instance
618
+ res = Dcmgr.messaging.submit("kvm-handle.#{i.host_pool.node_id}", 'detach', i.canonical_uuid, v.canonical_uuid)
619
+ respond_to { |f|
620
+ f.json {v.to_hash_document.to_json}
621
+ }
622
+ end
623
+ end
624
+
625
+ operation :status, :method =>:get, :member =>true do
626
+ description 'Show the status'
627
+ control do
628
+ vl = [{ :id => 1, :uuid => 'vol-xxxxxxx', :status => 1 },
629
+ { :id => 2, :uuid => 'vol-xxxxxxx', :status => 0 },
630
+ { :id => 3, :uuid => 'vol-xxxxxxx', :status => 3 },
631
+ { :id => 4, :uuid => 'vol-xxxxxxx', :status => 2 },
632
+ { :id => 5, :uuid => 'vol-xxxxxxx', :status => 4 }]
633
+ respond_to {|f|
634
+ f.json { vl.to_json}
635
+ }
636
+ end
637
+ end
638
+ end
639
+
640
+ collection :volume_snapshots do
641
+ operation :index do
642
+ description 'Show lists of the volume_snapshots'
643
+ # params start, fixnum, optional
644
+ # params limit, fixnum, optional
645
+ control do
646
+ start = params[:start].to_i
647
+ start = start < 1 ? 0 : start
648
+ limit = params[:limit].to_i
649
+ limit = limit < 1 ? nil : limit
650
+
651
+ total_vs = Models::VolumeSnapshot.where(:account_id => @account.canonical_uuid)
652
+ partial_vs = total_vs.dup.order(:id)
653
+ partial_vs = partial_vs.limit(limit, start) if limit.is_a?(Integer)
654
+ res = [{
655
+ :owner_total => total_vs.count,
656
+ :start => start,
657
+ :limit => limit,
658
+ :results => partial_vs.all.map { |vs| vs.to_hash_document}
659
+ }]
660
+ respond_to { |f|
661
+ f.json { res.to_json}
662
+ }
663
+ end
664
+ end
665
+
666
+ operation :show do
667
+ description 'Show the volume status'
668
+ # params id, string, required
669
+ control do
670
+ snapshot_id = params[:id]
671
+ raise UndefinedVolumeSnapshotID if snapshot_id.nil?
672
+ vs = find_by_uuid(:VolumeSnapshot, snapshot_id)
673
+ respond_to { |f|
674
+ f.json { vs.to_hash_document.to_json}
675
+ }
676
+ end
677
+ end
678
+
679
+ operation :create do
680
+ description 'Create a new volume snapshot'
681
+ # params volume_id, string, required
682
+ # params storage_pool_id, string, optional
683
+ control do
684
+ Models::Volume.lock!
685
+ raise UndefinedVolumeID if params[:volume_id].nil?
686
+
687
+ v = find_by_uuid(:Volume, params[:volume_id])
688
+ raise UnknownVolume if v.nil?
689
+
690
+ vs = v.create_snapshot(@account.canonical_uuid)
691
+ sp = vs.storage_pool
692
+
693
+ res = Dcmgr.messaging.submit("zfs-handle.#{sp.node_id}", 'create_snapshot', vs.canonical_uuid)
694
+ respond_to { |f|
695
+ f.json { vs.to_hash_document.to_json}
696
+ }
697
+ end
698
+ end
699
+
700
+ operation :destroy do
701
+ description 'Delete the volume snapshot'
702
+ # params id, string, required
703
+ control do
704
+ Models::VolumeSnapshot.lock!
705
+ snapshot_id = params[:id]
706
+ raise UndefindVolumeSnapshotID if snapshot_id.nil?
707
+
708
+ vs = find_by_uuid(:VolumeSnapshot, snapshot_id)
709
+ raise UnknownVolumeSnapshot if vs.nil?
710
+ vs = vs.delete_snapshot
711
+ sp = vs.storage_pool
712
+
713
+ res = Dcmgr.messaging.submit("zfs-handle.#{sp.node_id}", 'delete_snapshot', vs.canonical_uuid)
714
+ respond_to { |f|
715
+ f.json { vs.to_hash_document.to_json }
716
+ }
717
+ end
718
+ end
719
+
720
+ operation :status, :method =>:get, :member =>true do
721
+ description 'Show the status'
722
+ control do
723
+ vs = [{ :id => 1, :uuid => 'snap-xxxxxxx', :status => 1 },
724
+ { :id => 2, :uuid => 'snap-xxxxxxx', :status => 0 },
725
+ { :id => 3, :uuid => 'snap-xxxxxxx', :status => 3 },
726
+ { :id => 4, :uuid => 'snap-xxxxxxx', :status => 2 },
727
+ { :id => 5, :uuid => 'snap-xxxxxxx', :status => 4 }]
728
+ respond_to {|f|
729
+ f.json { vs.to_json}
730
+ }
731
+ end
732
+ end
733
+ end
734
+
735
+ collection :netfilter_groups do
736
+ description 'Show lists of the netfilter_groups'
737
+ operation :index do
738
+ control do
739
+ start = params[:start].to_i
740
+ start = start < 1 ? 0 : start
741
+ limit = params[:limit].to_i
742
+ limit = limit < 1 ? nil : limit
743
+
744
+ total_ds = Models::NetfilterGroup.where(:account_id=>@account.canonical_uuid)
745
+ partial_ds = total_ds.dup.order(:id)
746
+ partial_ds = partial_ds.limit(limit, start) if limit.is_a?(Integer)
747
+
748
+ res = [{
749
+ :owner_total => total_ds.count,
750
+ :start => start,
751
+ :limit => limit,
752
+ :results=> partial_ds.all.map {|i| i.to_hash }
753
+ }]
754
+
755
+ respond_to { |f|
756
+ f.json {res.to_json}
757
+ }
758
+ end
759
+ end
760
+
761
+ operation :show do
762
+ description 'Show the netfilter_groups'
763
+ control do
764
+ g = find_by_uuid(:NetfilterGroup, params[:id])
765
+ p params[:id]
766
+ raise OperationNotPermitted unless examine_owner(g)
767
+
768
+ respond_to { |f|
769
+ f.json { g.to_hash.to_json }
770
+ }
771
+ end
772
+ end
773
+
774
+ operation :create do
775
+ description 'Register a new netfilter_group'
776
+ # params name, string
777
+ # params description, string
778
+ # params rule, string
779
+ control do
780
+ Models::NetfilterGroup.lock!
781
+ raise UndefinedNetfilterGroup if params[:name].nil?
782
+
783
+ @name = params[:name]
784
+ # TODO: validate @name. @name can use [a-z] [A-Z] '_' '-'
785
+ # - invalidate? -> raise InvalidCharacterOfNetfilterGroupName
786
+
787
+ g = Models::NetfilterGroup.filter(:name => @name, :account_id => @account.canonical_uuid).first
788
+ raise DuplicatedNetfilterGroup unless g.nil?
789
+
790
+ g = Models::NetfilterGroup.create_group(@account.canonical_uuid, params)
791
+ respond_to { |f|
792
+ f.json { g.to_hash.to_json }
793
+ }
794
+ end
795
+ end
796
+
797
+ operation :update do
798
+ description "Update parameters for the netfilter group"
799
+ # params description, string
800
+ # params rule, string
801
+ control do
802
+ g = find_by_uuid(:NetfilterGroup, params[:id])
803
+
804
+ raise UnknownNetfilterGroup if g.nil?
805
+
806
+ if params[:description]
807
+ g.description = params[:description]
808
+ end
809
+ if params[:rule]
810
+ g.rule = params[:rule]
811
+ end
812
+
813
+ g.save
814
+ g.rebuild_rule
815
+
816
+ # refresh netfilter_rules
817
+ Dcmgr.messaging.event_publish('hva/netfilter_updated', :args=>[g.canonical_uuid])
818
+
819
+ respond_to { |f|
820
+ f.json { g.to_hash.to_json }
821
+ }
822
+ end
823
+ end
824
+
825
+ operation :destroy do
826
+ # params name, string
827
+ description "Delete the netfilter group"
828
+
829
+ control do
830
+ Models::NetfilterGroup.lock!
831
+ g = find_by_uuid(:NetfilterGroup, params[:id])
832
+
833
+ raise UnknownNetfilterGroup if g.nil?
834
+ raise NetfilterGroupNotPermitted if g.account_id != @account.canonical_uuid
835
+
836
+ respond_to { |f|
837
+ f.json { g.destroy_group.values.to_json }
838
+ }
839
+ end
840
+ end
841
+
842
+ end
843
+
844
+ collection :netfilter_rules do
845
+ operation :index do
846
+ control do
847
+ end
848
+ end
849
+
850
+ operation :show do
851
+ description 'Show lists of the netfilter_rules'
852
+ control do
853
+ rules = []
854
+ begin
855
+ @name = params[:id]
856
+ g = Models::NetfilterGroup.filter(:name => @name, :account_id => @account.canonical_uuid).first
857
+ raise UnknownNetfilterGroup if g.nil?
858
+
859
+ g.netfilter_rules.each { |rule|
860
+ rules << rule.values
861
+ }
862
+ end
863
+
864
+ respond_to { |f|
865
+ f.json { rules.to_json }
866
+ }
867
+ end
868
+ end
869
+ end
870
+
871
+ collection :storage_pools do
872
+ operation :index do
873
+ description 'Show lists of the storage_pools'
874
+ # params start, fixnum, optional
875
+ # params limit, fixnum, optional
876
+ control do
877
+ start = params[:start].to_i
878
+ start = start < 1 ? 0 : start
879
+ limit = params[:limit].to_i
880
+ limit = limit < 1 ? nil : limit
881
+
882
+ total_ds = Models::StoragePool.where(:account_id=>@account.canonical_uuid)
883
+ partial_ds = total_ds.dup.order(:id)
884
+ partial_ds = partial_ds.limit(limit, start) if limit.is_a?(Integer)
885
+
886
+ res = [{
887
+ :owner_total => total_ds.count,
888
+ :start => start,
889
+ :limit => limit,
890
+ :results=> partial_ds.all.map {|sp| sp.to_hash_document }
891
+ }]
892
+
893
+ respond_to { |f|
894
+ f.json { res.to_json}
895
+ }
896
+ end
897
+ end
898
+
899
+ operation :show do
900
+ description 'Show the storage_pool status'
901
+ # params id, string, required
902
+ control do
903
+ pool_id = params[:id]
904
+ raise UndefinedStoragePoolID if pool_id.nil?
905
+ vs = find_by_uuid(:StoragePool, pool_id)
906
+ raise UnknownStoragePool if vs.nil?
907
+ respond_to { |f|
908
+ f.json { vs.to_hash_document.to_json}
909
+ }
910
+ end
911
+ end
912
+ end
913
+
914
+ collection :ssh_key_pairs do
915
+ description "List ssh key pairs in account"
916
+ operation :index do
917
+ # params start, fixnum, optional
918
+ # params limit, fixnum, optional
919
+ control do
920
+ start = params[:start].to_i
921
+ start = start < 1 ? 0 : start
922
+ limit = params[:limit].to_i
923
+ limit = limit < 1 ? nil : limit
924
+
925
+ total_ds = Models::SshKeyPair.where(:account_id=>@account.canonical_uuid)
926
+ partial_ds = total_ds.dup.order(:id)
927
+ partial_ds = partial_ds.limit(limit, start) if limit.is_a?(Integer)
928
+
929
+ res = [{
930
+ :owner_total => total_ds.count,
931
+ :filter_total => total_ds.count,
932
+ :start => start,
933
+ :limit => limit,
934
+ :results=> partial_ds.all.map {|i| i.to_hash }
935
+ }]
936
+
937
+ respond_to { |f|
938
+ f.json {res.to_json}
939
+ }
940
+ end
941
+ end
942
+
943
+ operation :show do
944
+ description "Retrieve details about ssh key pair"
945
+ # params :id required
946
+ # params :format optional [openssh,putty]
947
+ control do
948
+ ssh = find_by_uuid(:SshKeyPair, params[:id])
949
+
950
+ respond_to { |f|
951
+ f.json {ssh.to_hash.to_json}
952
+ }
953
+ end
954
+ end
955
+
956
+ operation :create do
957
+ description "Create ssh key pair information"
958
+ # params :name required key name (<100 chars)
959
+ # params :download_once optional set true if you do not want
960
+ # to save private key info on database.
961
+ control do
962
+ Models::SshKeyPair.lock!
963
+ keydata = Models::SshKeyPair.generate_key_pair
964
+ savedata = {
965
+ :name=>params[:name],
966
+ :account_id=>@account.canonical_uuid,
967
+ :public_key=>keydata[:public_key]
968
+ }
969
+ if params[:download_once] != 'true'
970
+ savedata[:private_key]=keydata[:private_key]
971
+ end
972
+ ssh = Models::SshKeyPair.create(savedata)
973
+
974
+ respond_to { |f|
975
+ # include private_key data in response even if
976
+ # it's not going to be stored on DB.
977
+ f.json {ssh.to_hash.merge(:private_key=>keydata[:private_key]).to_json}
978
+ }
979
+ end
980
+ end
981
+
982
+ operation :destroy do
983
+ description "Remove ssh key pair information"
984
+ # params :id required
985
+ control do
986
+ Models::SshKeyPair.lock!
987
+ ssh = find_by_uuid(:SshKeyPair, params[:id])
988
+ if examine_owner(ssh)
989
+ ssh.destroy
990
+ else
991
+ raise OperationNotPermitted
992
+ end
993
+
994
+ respond_to { |f|
995
+ f.json {ssh.to_hash.to_json}
996
+ }
997
+ end
998
+ end
999
+
1000
+ end
1001
+
1002
+ end
1003
+ end
1004
+ end