occi 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.project +32 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/AUTHORS +8 -0
- data/Gemfile +35 -14
- data/Gemfile.lock +89 -32
- data/LICENSE +13 -4
- data/README.md +70 -21
- data/Rakefile +25 -21
- data/lib/OpenNebula/Acl.rb +256 -0
- data/lib/OpenNebula/AclPool.rb +53 -0
- data/lib/OpenNebula/Group.rb +147 -0
- data/lib/OpenNebula/GroupPool.rb +54 -0
- data/lib/OpenNebula/Host.rb +143 -0
- data/lib/OpenNebula/HostPool.rb +55 -0
- data/lib/OpenNebula/Image.rb +256 -0
- data/lib/OpenNebula/ImagePool.rb +74 -0
- data/lib/OpenNebula/OpenNebula.rb +137 -0
- data/lib/OpenNebula/Pool.rb +285 -0
- data/lib/OpenNebula/Template.rb +173 -0
- data/lib/OpenNebula/TemplatePool.rb +74 -0
- data/lib/OpenNebula/User.rb +157 -0
- data/lib/OpenNebula/UserPool.rb +53 -0
- data/lib/OpenNebula/VirtualMachine.rb +319 -0
- data/lib/OpenNebula/VirtualMachinePool.rb +120 -0
- data/lib/OpenNebula/VirtualNetwork.rb +229 -0
- data/lib/OpenNebula/VirtualNetworkPool.rb +74 -0
- data/lib/OpenNebula/XMLUtils.rb +337 -0
- data/lib/occi/antlr/.gitignore +18 -0
- data/lib/occi/antlr/OCCI.g +164 -0
- data/lib/occi/antlr/OCCI.tokens +75 -0
- data/lib/occi/antlr/OCCILexer.rb +1532 -0
- data/lib/occi/antlr/OCCIParser.rb +2472 -0
- data/lib/occi/antlr/README.md +2 -0
- data/lib/occi/backend/dummy.rb +232 -0
- data/lib/occi/backend/ec2/Compute.rb +310 -0
- data/lib/occi/backend/ec2/compute.rb +310 -0
- data/lib/occi/backend/ec2/ec2.rb +215 -0
- data/lib/occi/backend/manager.rb +131 -0
- data/lib/occi/backend/opennebula/compute.rb +360 -0
- data/lib/occi/backend/opennebula/network.rb +143 -0
- data/lib/occi/backend/opennebula/opennebula.rb +188 -0
- data/lib/occi/backend/opennebula/storage.rb +175 -0
- data/lib/occi/configuration.rb +118 -0
- data/lib/occi/core/action.rb +29 -0
- data/lib/occi/core/attribute_properties.rb +54 -0
- data/lib/occi/core/attributes.rb +40 -0
- data/lib/occi/core/category.rb +62 -0
- data/lib/occi/core/collection.rb +27 -0
- data/lib/occi/core/entity.rb +135 -0
- data/lib/occi/core/kind.rb +55 -0
- data/lib/occi/core/link.rb +88 -0
- data/lib/occi/core/mixin.rb +43 -0
- data/lib/occi/core/resource.rb +73 -0
- data/lib/occi/exceptions.rb +59 -0
- data/lib/occi/extensions/monitoring/cpu.rb +51 -0
- data/lib/occi/extensions/monitoring/memory.rb +49 -0
- data/lib/occi/extensions/monitoring/metric.rb +54 -0
- data/lib/occi/extensions/monitoring/netrx.rb +49 -0
- data/lib/occi/extensions/monitoring/nettx.rb +55 -0
- data/lib/occi/extensions/one/VNC.rb +58 -0
- data/lib/occi/extensions/one/vnc.rb +58 -0
- data/lib/occi/log.rb +47 -0
- data/lib/occi/parse.rb +164 -0
- data/lib/occi/registry.rb +87 -0
- data/lib/occi/server.rb +594 -0
- data/lib/occi/version.rb +3 -0
- data/occi.gemspec +18 -96
- data/spec/occi/antlr/parser_spec.rb +82 -0
- data/spec/spec_helper.rb +6 -0
- metadata +100 -208
- data/.autotest +0 -21
- data/.rvmrc +0 -1
- data/VERSION +0 -1
- data/lib/occi.rb +0 -1
- data/lib/occi/client.rb +0 -36
- data/lib/occi/compute.rb +0 -4
- data/lib/occi/network.rb +0 -4
- data/lib/occi/resource.rb +0 -59
- data/lib/occi/storage.rb +0 -7
- data/test/fixtures/cassettes/compute_all.yml +0 -28
- data/test/fixtures/cassettes/compute_create.yml +0 -45
- data/test/fixtures/cassettes/compute_destroy.yml +0 -22
- data/test/fixtures/cassettes/compute_find.yml +0 -28
- data/test/fixtures/cassettes/compute_update.yml +0 -35
- data/test/fixtures/cassettes/network_all.yml +0 -28
- data/test/fixtures/cassettes/network_create.yml +0 -37
- data/test/fixtures/cassettes/network_destroy.yml +0 -22
- data/test/fixtures/cassettes/network_find.yml +0 -28
- data/test/fixtures/cassettes/storage_all.yml +0 -28
- data/test/fixtures/cassettes/storage_create.yml +0 -50
- data/test/fixtures/cassettes/storage_destroy.yml +0 -22
- data/test/fixtures/cassettes/storage_find.yml +0 -28
- data/test/lib/occi/compute_test.rb +0 -91
- data/test/lib/occi/network_test.rb +0 -57
- data/test/lib/occi/storage_test.rb +0 -67
- data/test/test_helper.rb +0 -48
data/lib/occi/server.rb
ADDED
@@ -0,0 +1,594 @@
|
|
1
|
+
##############################################################################
|
2
|
+
# Copyright 2011 Service Computing group, TU Dortmund
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# 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,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##############################################################################
|
16
|
+
|
17
|
+
##############################################################################
|
18
|
+
# Description: OCCI RESTful Web Service
|
19
|
+
# Author(s): Hayati Bice, Florian Feldhaus, Piotr Kasprzak
|
20
|
+
##############################################################################
|
21
|
+
|
22
|
+
##############################################################################
|
23
|
+
# Require Ruby Gems
|
24
|
+
|
25
|
+
# gems
|
26
|
+
require 'rubygems'
|
27
|
+
|
28
|
+
# sinatra
|
29
|
+
require 'sinatra'
|
30
|
+
require 'sinatra/multi_route'
|
31
|
+
require 'sinatra/cross_origin'
|
32
|
+
require 'sinatra/respond_with'
|
33
|
+
|
34
|
+
# Ruby standard library
|
35
|
+
require 'uri'
|
36
|
+
require 'fileutils'
|
37
|
+
|
38
|
+
# Server configuration
|
39
|
+
require 'occi/configuration'
|
40
|
+
|
41
|
+
# Active support notifications
|
42
|
+
require 'active_support/notifications'
|
43
|
+
|
44
|
+
# Active support for xml rendering
|
45
|
+
require 'active_support/core_ext'
|
46
|
+
|
47
|
+
##############################################################################
|
48
|
+
# Require OCCI classes
|
49
|
+
|
50
|
+
# Exceptions
|
51
|
+
require 'occi/exceptions'
|
52
|
+
|
53
|
+
# Category registry
|
54
|
+
require 'occi/registry'
|
55
|
+
|
56
|
+
# OCCI Core classes
|
57
|
+
require 'occi/core/action'
|
58
|
+
require 'occi/core/category'
|
59
|
+
require 'occi/core/entity'
|
60
|
+
require 'occi/core/kind'
|
61
|
+
require 'occi/core/link'
|
62
|
+
require 'occi/core/mixin'
|
63
|
+
require 'occi/core/resource'
|
64
|
+
|
65
|
+
# OCCI parser
|
66
|
+
require 'occi/parse'
|
67
|
+
|
68
|
+
# Backend support
|
69
|
+
require 'occi/backend/manager'
|
70
|
+
|
71
|
+
##############################################################################
|
72
|
+
# Sinatra methods for handling HTTP requests
|
73
|
+
|
74
|
+
module OCCI
|
75
|
+
class Server < Sinatra::Application
|
76
|
+
|
77
|
+
register Sinatra::MultiRoute
|
78
|
+
register Sinatra::CrossOrigin
|
79
|
+
register Sinatra::RespondWith
|
80
|
+
|
81
|
+
enable cross_origin
|
82
|
+
|
83
|
+
# Read configuration file
|
84
|
+
def self.config
|
85
|
+
@@config ||= OCCI::Configuration.new('etc/occi-server.conf')
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.location
|
89
|
+
OCCI::Server.config[:server].chomp('/')
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.port
|
93
|
+
OCCI::Server.config[:port]
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.uri
|
97
|
+
self.port.nil? ? self.location : self.location + ':' + self.port
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(config = {})
|
101
|
+
# create logger
|
102
|
+
config[:log_dest] ||= STDOUT
|
103
|
+
config[:log_level] ||= Logger::INFO
|
104
|
+
config[:log_level] = case OCCI::Server.config[:log_level]
|
105
|
+
when "debug"
|
106
|
+
Logger::DEBUG
|
107
|
+
when "info"
|
108
|
+
Logger::INFO
|
109
|
+
when "warn"
|
110
|
+
Logger::WARN
|
111
|
+
when "error"
|
112
|
+
Logger::ERROR
|
113
|
+
when "fatal"
|
114
|
+
Logger::FATAL
|
115
|
+
else
|
116
|
+
Logger::INFO
|
117
|
+
end
|
118
|
+
|
119
|
+
@logger = Logger.new(config[:log_dest])
|
120
|
+
@logger.level = config[:log_level]
|
121
|
+
|
122
|
+
# subscribe to log messages and send to logger
|
123
|
+
@log_subscriber = ActiveSupport::Notifications.subscribe("log") do |name, start, finish, id, payload|
|
124
|
+
@logger.log(payload[:level], payload[:message])
|
125
|
+
end
|
126
|
+
|
127
|
+
# Configuration of HTTP Authentication
|
128
|
+
if OCCI::Server.config['username'] != nil and OCCI::Server.config['password'] != nil
|
129
|
+
use Rack::Auth::Basic, "Restricted Area" do |username, password|
|
130
|
+
[username, password] == [OCCI::Server.config['username'], OCCI::Server.config['password']]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
OCCI::Server.initialize_core_model
|
135
|
+
OCCI::Server.initialize_model(OCCI::Server.config['occi_model_path'])
|
136
|
+
|
137
|
+
# set views explicitly
|
138
|
+
set :views, File.dirname(__FILE__) + "/../../views"
|
139
|
+
|
140
|
+
super
|
141
|
+
end
|
142
|
+
|
143
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
144
|
+
|
145
|
+
def self.initialize_core_model
|
146
|
+
OCCI::Log.info("### Initializing OCCI Core Model ###")
|
147
|
+
OCCI::Core::Entity.register
|
148
|
+
OCCI::Core::Resource.register
|
149
|
+
OCCI::Core::Link.register
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.initialize_model(path)
|
153
|
+
OCCI::Log.info("### Initializing OCCI Model from #{path} ###")
|
154
|
+
Dir.glob(path + '/**/*.json').each do |file|
|
155
|
+
collection = Hashie::Mash.new(JSON.parse(File.read(file)))
|
156
|
+
# add location of service provider to scheme if it has a relative location
|
157
|
+
collection.kinds.collect { |kind| kind.scheme = self.location + kind.scheme if kind.scheme.start_with? '/' } if collection.kinds
|
158
|
+
collection.mixins.collect { |mixin| mixin.scheme = self.location + mixin.scheme if mixin.scheme.start_with? '/' } if collection.mixins
|
159
|
+
collection.actions.collect { |action| action.scheme = self.location + action.scheme if action.scheme.start_with? '/' } if collection.actions
|
160
|
+
# register categories
|
161
|
+
collection.kinds.each { |kind| OCCI::Registry.register(OCCI::Core::Kind.new(kind)) } if collection.kinds
|
162
|
+
collection.mixins.each { |mixin| OCCI::Registry.register(OCCI::Core::Mixin.new(mixin)) } if collection.mixins
|
163
|
+
collection.actions.each { |action| OCCI::Registry.register(OCCI::Core::Action.new(action)) } if collection.actions
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
168
|
+
def initialize_backend(auth)
|
169
|
+
|
170
|
+
if auth.provided? && auth.basic? && auth.credentials
|
171
|
+
user, password = auth.credentials
|
172
|
+
else
|
173
|
+
user, password = [OCCI::Server.config['one_user'], OCCI::Server.config['one_password']]
|
174
|
+
logger.debug("No basic auth data provided: using defaults from config (user = '#{user}')")
|
175
|
+
end
|
176
|
+
|
177
|
+
@backend = case OCCI::Server.config["backend"]
|
178
|
+
when "opennebula"
|
179
|
+
require 'occi/backend/opennebula/opennebula'
|
180
|
+
OCCI::Server.initialize_model('etc/backend/opennebula')
|
181
|
+
OCCI::Backend::Manager.register_backend(OCCI::Backend::OpenNebula::OpenNebula, OCCI::Backend::OpenNebula::OpenNebula::OPERATIONS)
|
182
|
+
OCCI::Backend::OpenNebula::OpenNebula.new(user, password)
|
183
|
+
when "ec2"
|
184
|
+
require 'occi/backend/ec2/ec2'
|
185
|
+
Bundler.require(:ec2)
|
186
|
+
OCCI::Server.initialize_model('etc/backend/ec2')
|
187
|
+
OCCI::Backend::Manager.register_backend(OCCI::Backend::EC2::EC2, OCCI::Backend::EC2::EC2::OPERATIONS)
|
188
|
+
OCCI::Backend::EC2::EC2.new(user, password)
|
189
|
+
when "dummy" then
|
190
|
+
require 'occi/backend/dummy'
|
191
|
+
OCCI::Backend::Manager.register_backend(OCCI::Backend::Dummy, OCCI::Backend::Dummy::OPERATIONS)
|
192
|
+
OCCI::Backend::Dummy.new()
|
193
|
+
else
|
194
|
+
raise "Backend '" + OCCI::Server.config["backend"] + "' not found"
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
# Parses a Rack/Sinatra Request and extract OCCI relevant information
|
201
|
+
def parse(request)
|
202
|
+
OCCI::Log.debug('### Parsing request data to OCCI data structure ###')
|
203
|
+
body = request.body.read
|
204
|
+
header = request.env
|
205
|
+
collection = OCCI::Core::Collection.new
|
206
|
+
locations = []
|
207
|
+
# always check headers
|
208
|
+
locations = OCCI::Parse.header_locations(header)
|
209
|
+
if locations.empty?
|
210
|
+
if request.path_info.include?('/-/')
|
211
|
+
collection = OCCI::Parse.header_categories(header)
|
212
|
+
else
|
213
|
+
collection = OCCI::Parse.header_entity(header)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
case request.media_type
|
218
|
+
when 'text/uri-list'
|
219
|
+
body.each_line do |line|
|
220
|
+
locations << URI.parse(line)
|
221
|
+
end
|
222
|
+
when 'text/plain', nil
|
223
|
+
locations = OCCI::Parse.text_locations(body)
|
224
|
+
if locations.empty?
|
225
|
+
if request.path_info.include?('/-/')
|
226
|
+
collection = OCCI::Parse.text_categories(body)
|
227
|
+
else
|
228
|
+
collection = OCCI::Parse.text_entity(body)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
when 'application/occi+json', 'application/json'
|
232
|
+
collection = OCCI::Parse.json(body)
|
233
|
+
when 'application/occi+xml', 'application/xml'
|
234
|
+
collection = OCCI::Parse.xml(body)
|
235
|
+
else
|
236
|
+
raise OCCI::ContentTypeNotSupported
|
237
|
+
end
|
238
|
+
return locations, collection
|
239
|
+
end
|
240
|
+
|
241
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
242
|
+
|
243
|
+
# GET request
|
244
|
+
|
245
|
+
# tasks to be executed before the request is handled
|
246
|
+
before do
|
247
|
+
OCCI::Log.debug('--------------------------------------------------------------------')
|
248
|
+
OCCI::Log.debug("### Client IP: #{request.ip}")
|
249
|
+
OCCI::Log.debug("### Client Accept: #{request.accept}")
|
250
|
+
OCCI::Log.debug("### Client User Agent: #{request.user_agent}")
|
251
|
+
OCCI::Log.debug("### Client Request URL: #{request.url}")
|
252
|
+
OCCI::Log.debug("### Client Request method: #{request.request_method}")
|
253
|
+
OCCI::Log.debug("### Client Request Media Type: #{request.media_type}")
|
254
|
+
OCCI::Log.debug('--------------------------------------------------------------------')
|
255
|
+
|
256
|
+
OCCI::Log.debug('### Prepare response ###')
|
257
|
+
response['Accept'] = "application/occi+json,application/json,text/plain,text/uri-list,application/xml,text/xml,application/occi+xml"
|
258
|
+
response['Server'] = "rOCCI/#{VERSION_NUMBER} OCCI/1.1"
|
259
|
+
OCCI::Log.debug('### Initialize response OCCI collection ###')
|
260
|
+
@collection = Hashie::Mash.new(:kinds => [], :mixins => [], :actions => [], :resources => [], :links => [])
|
261
|
+
@locations = Array.new
|
262
|
+
OCCI::Log.debug('### Preparing authentication handling ###')
|
263
|
+
authentication = Rack::Auth::Basic::Request.new(request.env)
|
264
|
+
OCCI::Log.debug('### Initializing backend ###')
|
265
|
+
initialize_backend(authentication)
|
266
|
+
OCCI::Log.debug('### Reset OCCI model ###')
|
267
|
+
OCCI::Registry.reset
|
268
|
+
@request_locations, @request_collection = parse(request)
|
269
|
+
OCCI::Log.debug('### Fill OCCI model with entities from backend ###')
|
270
|
+
@backend.register_existing_resources
|
271
|
+
end
|
272
|
+
|
273
|
+
after do
|
274
|
+
# OCCI::Log.debug((@collection.resources.to_a + @collection.links.to_a).collect {|entity| entity.location}.join("\n"))
|
275
|
+
OCCI::Log.debug('### Rendering response ###')
|
276
|
+
@collection.delete_if { |k, v| v.empty? } # remove empty entries
|
277
|
+
respond_to do |f|
|
278
|
+
f.txt { erb :collection, :locals => {:collection => @collection, :locations => @locations} }
|
279
|
+
f.on('*/*') { erb :collection, :locals => {:collection => @collection, :locations => @locations} }
|
280
|
+
# f.html { haml :collection, :locals => {:collection => @collection} }
|
281
|
+
f.json { @collection.to_json }
|
282
|
+
f.on('application/occi+json') { @collection.to_json }
|
283
|
+
f.xml { @collection.to_xml(:root => "collection") }
|
284
|
+
f.on('application/occi+xml') { @collection.to_xml(:root => "collection") }
|
285
|
+
f.on('text/uri-list') { @locations.join("\n") }
|
286
|
+
end
|
287
|
+
OCCI::Log.debug('### Successfully rendered ###')
|
288
|
+
end
|
289
|
+
|
290
|
+
# discovery interface
|
291
|
+
# returns all kinds, mixins and actions registered for the server
|
292
|
+
get '/-/', '/.well-known/org/ogf/occi/-/' do
|
293
|
+
OCCI::Log.info("### Listing all kinds, mixins and actions ###")
|
294
|
+
@collection = OCCI::Registry.get(@request_collection.categories)
|
295
|
+
status 200
|
296
|
+
end
|
297
|
+
|
298
|
+
# Resource retrieval
|
299
|
+
# returns entities either below a certain path or belonging to a certain kind or mixin
|
300
|
+
get '*' do
|
301
|
+
if request.path_info.end_with?('/')
|
302
|
+
if request.path_info == '/'
|
303
|
+
kinds = OCCI::Registry.get.kinds
|
304
|
+
else
|
305
|
+
kinds = [OCCI::Registry.get_by_location(request.path_info)]
|
306
|
+
end
|
307
|
+
|
308
|
+
kinds.each do |kind|
|
309
|
+
OCCI::Log.info("### Listing all entities of kind #{kind.type_identifier} ###")
|
310
|
+
@collection.resources.concat kind.entities if kind.entity_type == OCCI::Core::Resource.name
|
311
|
+
@collection.links.concat kind.entities if kind.entity_type == OCCI::Core::Link.name
|
312
|
+
@locations.concat kind.entities.collect { |entity| OCCI::Server.uri + entity.location }
|
313
|
+
end
|
314
|
+
else
|
315
|
+
kind = OCCI::Registry.get_by_location(request.path_info.rpartition('/').first + '/')
|
316
|
+
uuid = request.path_info.rpartition('/').last
|
317
|
+
error 404 if kind.nil? or uuid.nil?
|
318
|
+
OCCI::Log.info("### Listing entity with uuid #{uuid} ###")
|
319
|
+
@collection.resources = kind.entities.select { |entity| entity.id == uuid } if kind.entity_type == OCCI::Core::Resource.name
|
320
|
+
@collection.links = kind.entities.select { |entity| entity.id == uuid } if kind.entity_type == OCCI::Core::Link.name
|
321
|
+
end
|
322
|
+
status 200
|
323
|
+
end
|
324
|
+
|
325
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
326
|
+
# POST request
|
327
|
+
post '/-/', '/.well-known/org/ogf/occi/-/' do
|
328
|
+
logger.info("## Creating user defined mixin ###")
|
329
|
+
raise OCCI::MixinAlreadyExistsError, "Mixin already exists!" if OCCI::Registry.get(@request_collection.mixins)
|
330
|
+
@request_collection.mixins.each do |mixin|
|
331
|
+
OCCI::CategoryRegistry.register(mixin)
|
332
|
+
# TODO: inform backend about new mixin
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Create an instance appropriate to category field and optionally link an instance to another one
|
337
|
+
post '*' do
|
338
|
+
|
339
|
+
category = OCCI::Registry.get_by_location(request.path_info.rpartition('/').first + '/')
|
340
|
+
|
341
|
+
if category.nil?
|
342
|
+
OCCI::Log.debug("### No category found for request location #{request.path_info} ###")
|
343
|
+
status 404
|
344
|
+
end
|
345
|
+
|
346
|
+
# if action
|
347
|
+
if params[:action]
|
348
|
+
if @request_collection.actions.any?
|
349
|
+
action = @request_collection.actions.first
|
350
|
+
params[:method] ||= action.attributes!.method if action
|
351
|
+
else
|
352
|
+
action = OCCI::Registry.get_by_id(category.actions.select { |action| action.split('#').last == params[:action] }.first)
|
353
|
+
end
|
354
|
+
|
355
|
+
category.entities.each do |entity|
|
356
|
+
OCCI::Backend::Manager.delegate_action(@backend, action, params, entity)
|
357
|
+
status 200
|
358
|
+
end
|
359
|
+
elsif category.kind_of?(OCCI::Core::Kind)
|
360
|
+
@request_collection.resources.each do |resource|
|
361
|
+
OCCI::Log.debug("Deploying resource with title #{resource.title} in backend #{@backend.class.name}")
|
362
|
+
OCCI::Backend::Manager.signal_resource(@backend, OCCI::Backend::RESOURCE_DEPLOY, resource)
|
363
|
+
@locations << OCCI::Server.uri + resource.location
|
364
|
+
status 201
|
365
|
+
end
|
366
|
+
elsif category.kind_of?(OCCI::Core::Mixin)
|
367
|
+
@request_collection.locations.each do |location|
|
368
|
+
OCCI::Log.debug("Attaching resource #{resource.title} to mixin #{mixin.type_identifier} in backend #{@backend.class.name}")
|
369
|
+
# TODO: let backend carry out tasks related to the added mixin
|
370
|
+
category.entities << OCCI::Rendering::HTTP::LocationRegistry.get_object(location)
|
371
|
+
status 200
|
372
|
+
end
|
373
|
+
else
|
374
|
+
status 400
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
|
379
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
380
|
+
# PUT request
|
381
|
+
|
382
|
+
put '*' do
|
383
|
+
|
384
|
+
# Add an resource instance to a mixin
|
385
|
+
unless @occi_request.mixins.empty?
|
386
|
+
mixin = OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info)
|
387
|
+
|
388
|
+
@occi_request.locations.each do |location|
|
389
|
+
entity = OCCI::Rendering::HTTP::LocationRegistry.get_object(URI.parse(location).path)
|
390
|
+
|
391
|
+
raise "No entity found at location: #{entity_location}" if entity == nil
|
392
|
+
raise "Object referenced by uri [#{entity_location}] is not a OCCI::Core::Resource instance!" if !entity.kind_of?(OCCI::Core::Resource)
|
393
|
+
|
394
|
+
logger.debug("Associating entity [#{entity}] at location #{entity_location} with mixin #{mixin}")
|
395
|
+
|
396
|
+
entity.mixins << mixin
|
397
|
+
end
|
398
|
+
break
|
399
|
+
end
|
400
|
+
|
401
|
+
# Update resource instance(s) at the given location
|
402
|
+
unless OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info).nil?
|
403
|
+
entities = []
|
404
|
+
# Determine set of resources to be updated
|
405
|
+
if OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info).kind_of?(OCCI::Core::Resource)
|
406
|
+
entities = [OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info)]
|
407
|
+
elsif not OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info).kind_of?(OCCI::Core::Category)
|
408
|
+
entities = OCCI::Rendering::HTTP::LocationRegistry.get_resources_below_location(request.path_info, OCCI::CategoryRegistry.get_all)
|
409
|
+
elsif OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info).kind_of?(OCCI::Core::Category)
|
410
|
+
object = OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info)
|
411
|
+
@occi_request.locations.each do |loc|
|
412
|
+
entities << OCCI::Rendering::HTTP::LocationRegistry.get_object(URI.parse(loc.chomp('"').reverse.chomp('"').reverse).path)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
logger.info("Full update for [#{entities.size}] entities...")
|
416
|
+
|
417
|
+
# full update of mixins
|
418
|
+
object.entities.each do |entity|
|
419
|
+
entity.mixins.delete(object)
|
420
|
+
object.entities.delete(entity)
|
421
|
+
end if object.kind_of?(OCCI::Core::Mixin)
|
422
|
+
|
423
|
+
entities.each do |entity|
|
424
|
+
logger.debug("Adding entity: #{entity.get_location} to mixin #{object.type_identifier}")
|
425
|
+
entity.mixins.push(object).uniq!
|
426
|
+
object.entities.push(entity).uniq!
|
427
|
+
end if object.kind_of?(OCCI::Core::Mixin)
|
428
|
+
|
429
|
+
# full update of attributes
|
430
|
+
entities.each do |entity|
|
431
|
+
# Refresh information from backend for entities of type resource
|
432
|
+
# TODO: full update
|
433
|
+
entity.attributes.merge!(@occi_request.attributes)
|
434
|
+
# TODO: update entity in backend
|
435
|
+
end unless @occi_request.attributes.empty?
|
436
|
+
|
437
|
+
# full update of links
|
438
|
+
# TODO: full update e.g. delete old links first
|
439
|
+
@occi_request.links.each do |link_data|
|
440
|
+
logger.debug("Extracted link data: #{link_data}")
|
441
|
+
raise "Mandatory information missing (related | target | category)!" unless link_data.related != nil && link_data.target != nil && link_data.category != nil
|
442
|
+
|
443
|
+
link_mixins = []
|
444
|
+
link_kind = nil
|
445
|
+
link_data.category.split(' ').each do |link_category|
|
446
|
+
begin
|
447
|
+
cat = OCCI::CategoryRegistry.get_by_id(link_category)
|
448
|
+
rescue OCCI::CategoryNotFoundException => e
|
449
|
+
logger.info("Category #{link_category} not found")
|
450
|
+
next
|
451
|
+
end
|
452
|
+
link_kind = cat if cat.kind_of?(OCCI::Core::Kind)
|
453
|
+
link_mixins << cat if cat.kind_of?(OCCI::Core::Mixin)
|
454
|
+
end
|
455
|
+
|
456
|
+
raise "No kind for link category #{link_data.category} found" if link_kind.nil?
|
457
|
+
|
458
|
+
target_location = link_data.target_attr
|
459
|
+
target = OCCI::Rendering::HTTP::LocationRegistry.get_object(target_location)
|
460
|
+
|
461
|
+
entities.each do |entity|
|
462
|
+
|
463
|
+
source_location = OCCI::Rendering::HTTP::LocationRegistry.get_location_of_object(entity)
|
464
|
+
|
465
|
+
link_attributes = link_data.attributes.clone
|
466
|
+
link_attributes["occi.core.target"] = target_location.chomp('"').reverse.chomp('"').reverse
|
467
|
+
link_attributes["occi.core.source"] = source_location
|
468
|
+
|
469
|
+
link = link_kind.entity_type.new(link_attributes, link_mixins)
|
470
|
+
OCCI::Rendering::HTTP::LocationRegistry.register_location(link.get_location(), link)
|
471
|
+
|
472
|
+
target.links << link
|
473
|
+
entity.links << link
|
474
|
+
end
|
475
|
+
end
|
476
|
+
break
|
477
|
+
end
|
478
|
+
|
479
|
+
response.status = OCCI::Rendering::HTTP::Response::HTTP_NOT_FOUND
|
480
|
+
# Create resource instance at the given location
|
481
|
+
raise "Creating resources with method 'put' is currently not possible!"
|
482
|
+
|
483
|
+
# This must be the last statement in this block, so that sinatra does not try to respond with random body content
|
484
|
+
# (or fail utterly while trying to do that!)
|
485
|
+
nil
|
486
|
+
|
487
|
+
end
|
488
|
+
|
489
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
490
|
+
# DELETE request
|
491
|
+
|
492
|
+
delete '/-/', '/.well-known/org/ogf/occi/-/' do
|
493
|
+
# Location references query interface => delete provided mixin
|
494
|
+
raise OCCI::CategoryMissingException if @request_collection.mixins.nil?
|
495
|
+
mixins = OCCI::Registry.get(@request_collection.mixins)
|
496
|
+
raise OCCI::MixinNotFoundException if mixins.nil?
|
497
|
+
mixins.each do |mixin|
|
498
|
+
OCCI::Log.debug("### Deleting mixin #{mixin.type_identifier} ###")
|
499
|
+
mixin.entities.each do |entity|
|
500
|
+
entity.mixins.delete(mixin)
|
501
|
+
end
|
502
|
+
# TODO: Notify backend to delete mixin and unassociate entities
|
503
|
+
OCCI::Registry.unregister(mixin)
|
504
|
+
end
|
505
|
+
status 200
|
506
|
+
end
|
507
|
+
|
508
|
+
delete '*' do
|
509
|
+
|
510
|
+
# unassociate resources specified by URI in payload from mixin specified by request location
|
511
|
+
if request.path_info == '/'
|
512
|
+
categories = OCCI::Registry.get.kinds
|
513
|
+
else
|
514
|
+
categories = [OCCI::Registry.get_by_location(request.path_info.rpartition('/').first + '/')]
|
515
|
+
end
|
516
|
+
|
517
|
+
categories.each do |category|
|
518
|
+
case category
|
519
|
+
when OCCI::Core::Mixin
|
520
|
+
mixin = category
|
521
|
+
OCCI::Log.debug("### Deleting entities from mixin #{mixin.type_identifier} ###")
|
522
|
+
@request_collection.locations.each do |location|
|
523
|
+
uuid = location.to_s.rpartition('/').last
|
524
|
+
mixin.entities.delete_if { |entity| entity.id == uuid }
|
525
|
+
end
|
526
|
+
when OCCI::Core::Kind
|
527
|
+
kind = category
|
528
|
+
if request.path_info.end_with?('/')
|
529
|
+
if @request_collection.mixins.any?
|
530
|
+
@request_collection.mixins.each do |mixin|
|
531
|
+
OCCI::Log.debug("### Deleting entities from kind #{kind.type_identifier} with mixin #{mixin.type_identifier} ###")
|
532
|
+
kind.entities.each { |entity| OCCI::Backend::Manager.signal_resource(@backend, OCCI::Backend::RESOURCE_DELETE, entity) if mixin.include?(entity) }
|
533
|
+
kind.entities.delete_if? { |entity| mixin.include?(entity) }
|
534
|
+
# TODO: links
|
535
|
+
end
|
536
|
+
else
|
537
|
+
# TODO: links
|
538
|
+
OCCI::Log.debug("### Deleting entities from kind #{kind.type_identifier} ###")
|
539
|
+
kind.entities.each { |resource| OCCI::Backend::Manager.signal_resource(@backend, OCCI::Backend::RESOURCE_DELETE, resource) }
|
540
|
+
kind.entities.clear
|
541
|
+
end
|
542
|
+
else
|
543
|
+
uuid = request.path_info.rpartition('/').last
|
544
|
+
OCCI::Log.debug("### Deleting entity with id #{uuid} from kind #{kind.type_identifier} ###")
|
545
|
+
OCCI::Backend::Manager.signal_resource(@backend, OCCI::Backend::RESOURCE_DELETE, entity)
|
546
|
+
kind.entities.delete_if? { |entity| entity.id == uuid }
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
# delete entities
|
552
|
+
|
553
|
+
|
554
|
+
# # Location references a mixin => unassociate all provided resources (by X_OCCI_LOCATION) from it
|
555
|
+
# object = OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info)
|
556
|
+
# if object != nil && object.kind_of?(OCCI::Core::Mixin)
|
557
|
+
# mixin = OCCI::Rendering::HTTP::LocationRegistry.get_object(request.path_info)
|
558
|
+
# logger.info("Unassociating entities from mixin: #{mixin}")
|
559
|
+
#
|
560
|
+
# @occi_request.locations.each do |loc|
|
561
|
+
# entity = OCCI::Rendering::HTTP::LocationRegistry.get_object(URI.parse(loc.chomp('"').reverse.chomp('"').reverse).path)
|
562
|
+
# mixin.entities.delete(entity)
|
563
|
+
# entity.mixins.delete(mixin)
|
564
|
+
# end
|
565
|
+
# break
|
566
|
+
# end
|
567
|
+
#
|
568
|
+
# entities = OCCI::Rendering::HTTP::LocationRegistry.get_resources_below_location(request.path_info, @occi_request.categories)
|
569
|
+
#
|
570
|
+
# unless entities.nil?
|
571
|
+
# entities.each do |entity|
|
572
|
+
# location = entity.get_location
|
573
|
+
# OCCI::Backend::Manager.signal_resource(@backend, OCCI::Backend::RESOURCE_DELETE, entity) if entity.kind_of? OCCI::Core::Resource
|
574
|
+
# # TODO: delete links in backend!
|
575
|
+
# entity.delete
|
576
|
+
# OCCI::Rendering::HTTP::LocationRegistry.unregister(location)
|
577
|
+
# end
|
578
|
+
# break
|
579
|
+
# end
|
580
|
+
#
|
581
|
+
# response.status = OCCI::Rendering::HTTP::Response::HTTP_NOT_FOUND
|
582
|
+
# # This must be the last statement in this block, so that sinatra does not try to respond with random body content
|
583
|
+
# # (or fail utterly while trying to do that!)
|
584
|
+
# nil
|
585
|
+
|
586
|
+
end
|
587
|
+
|
588
|
+
error do
|
589
|
+
OCCI::Log.error(sinatra.error)
|
590
|
+
'Sorry there was a nasty error - ' + env['sinatra.error'].name
|
591
|
+
end
|
592
|
+
|
593
|
+
end
|
594
|
+
end
|