You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2012/05/15 17:15:34 UTC
[PATCH core 02/26] Core: Added new Sinatra::Base Deltacloud collection based on Sinatra::Rabbit
From: Michal Fojtik <mf...@redhat.com>
Signed-off-by: Michal fojtik <mf...@redhat.com>
---
server/config.ru | 44 +++-
server/lib/deltacloud/base_driver/base_driver.rb | 250 ------------------
server/lib/deltacloud/base_driver/exceptions.rb | 191 --------------
server/lib/deltacloud/base_driver/features.rb | 276 --------------------
server/lib/deltacloud/collections.rb | 54 ++++
server/lib/deltacloud/collections/addresses.rb | 83 ++++++
server/lib/deltacloud/collections/buckets.rb | 215 +++++++++++++++
server/lib/deltacloud/collections/drivers.rb | 51 ++++
server/lib/deltacloud/collections/firewalls.rb | 116 ++++++++
.../deltacloud/collections/hardware_profiles.rb | 27 ++
server/lib/deltacloud/collections/images.rb | 70 +++++
.../lib/deltacloud/collections/instance_states.rb | 57 ++++
server/lib/deltacloud/collections/instances.rb | 103 ++++++++
server/lib/deltacloud/collections/keys.rb | 61 +++++
.../lib/deltacloud/collections/load_balancers.rb | 85 ++++++
server/lib/deltacloud/collections/realms.rb | 27 ++
.../deltacloud/collections/storage_snapshots.rb | 51 ++++
.../lib/deltacloud/collections/storage_volumes.rb | 99 +++++++
server/lib/deltacloud/drivers/base_driver.rb | 265 +++++++++++++++++++
server/lib/deltacloud/drivers/exceptions.rb | 191 ++++++++++++++
server/lib/deltacloud/helpers.rb | 86 +++++-
server/lib/deltacloud/models.rb | 26 +-
server/lib/deltacloud/models/hardware_profile.rb | 194 ++++++++++++++
server/lib/deltacloud/models/state_machine.rb | 99 +++++++
server/lib/deltacloud/state_machine.rb | 115 --------
25 files changed, 1974 insertions(+), 862 deletions(-)
delete mode 100644 server/lib/deltacloud/base_driver/base_driver.rb
delete mode 100644 server/lib/deltacloud/base_driver/exceptions.rb
delete mode 100644 server/lib/deltacloud/base_driver/features.rb
create mode 100644 server/lib/deltacloud/collections.rb
create mode 100644 server/lib/deltacloud/collections/addresses.rb
create mode 100644 server/lib/deltacloud/collections/buckets.rb
create mode 100644 server/lib/deltacloud/collections/drivers.rb
create mode 100644 server/lib/deltacloud/collections/firewalls.rb
create mode 100644 server/lib/deltacloud/collections/hardware_profiles.rb
create mode 100644 server/lib/deltacloud/collections/images.rb
create mode 100644 server/lib/deltacloud/collections/instance_states.rb
create mode 100644 server/lib/deltacloud/collections/instances.rb
create mode 100644 server/lib/deltacloud/collections/keys.rb
create mode 100644 server/lib/deltacloud/collections/load_balancers.rb
create mode 100644 server/lib/deltacloud/collections/realms.rb
create mode 100644 server/lib/deltacloud/collections/storage_snapshots.rb
create mode 100644 server/lib/deltacloud/collections/storage_volumes.rb
create mode 100644 server/lib/deltacloud/drivers/base_driver.rb
create mode 100644 server/lib/deltacloud/drivers/exceptions.rb
create mode 100644 server/lib/deltacloud/models/hardware_profile.rb
create mode 100644 server/lib/deltacloud/models/state_machine.rb
delete mode 100644 server/lib/deltacloud/state_machine.rb
diff --git a/server/config.ru b/server/config.ru
index a1f9efd..f17de43 100644
--- a/server/config.ru
+++ b/server/config.ru
@@ -14,14 +14,46 @@
# License for the specific language governing permissions and limitations
# under the License.
-require 'rubygems'
+# The default URL prefix (where to mount Deltacloud API)
-$top_srcdir ||= File::expand_path(File.dirname(__FILE__))
+# The default driver is 'mock'
+ENV['API_DRIVER'] ||= 'mock'
-$:.unshift File.join($top_srcdir, 'lib')
+# Set the API frontend use ('cimi' or 'deltacloud', default is 'deltacloud')
+ENV['API_FRONTEND'] ||= 'deltacloud'
-server_dir = ENV['API_FRONTEND'] == 'cimi' ? 'cimi' : 'deltacloud'
+# We need to set different API version and entrypoint location
+if ENV['API_FRONTEND'] == 'deltacloud'
+ API_VERSION = "9.9.9"
+ API_ROOT_URL = "/api"
+elsif ENV['API_FRONTEND'] == 'cimi'
+ API_VERSION = "1.0.0"
+ API_ROOT_URL = "/cloudEntryPoint"
+end
-load File.join($top_srcdir, 'lib', server_dir, 'server.rb')
+#begin
+ require File.join(File.dirname(__FILE__), 'lib', ENV['API_FRONTEND'], 'server.rb')
+#rescue LoadError => e
+# puts "[ERROR] The specified frontend (#{ENV['API_FRONTEND']}) not supported (#{e.message})"
+# exit 1
+#end
-run Sinatra::Application
+if self.respond_to? :map
+ map "/" do
+ class IndexEntrypoint < Sinatra::Base
+ get "/" do
+ redirect API_ROOT_URL, 301
+ end
+ end
+ run IndexEntrypoint
+ end
+
+ map API_ROOT_URL do
+ use Rack::Static, :urls => ["/stylesheets", "/javascripts"], :root => "public"
+ use Rack::Session::Cookie
+ case ENV['API_FRONTEND']
+ when 'deltacloud' then run Rack::Cascade.new([Deltacloud::API])
+ when 'cimi' then run Rack::Cascade.new([CIMI::API])
+ end
+ end
+end
diff --git a/server/lib/deltacloud/base_driver/base_driver.rb b/server/lib/deltacloud/base_driver/base_driver.rb
deleted file mode 100644
index 01dd5e7..0000000
--- a/server/lib/deltacloud/base_driver/base_driver.rb
+++ /dev/null
@@ -1,250 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-require 'deltacloud/base_driver/exceptions'
-
-module Deltacloud
-
- class BaseDriver
-
- include ExceptionHandler
-
- STATE_MACHINE_OPTS = {
- :all_states => [:start, :pending, :running, :stopping, :stopped, :finish],
- :all_actions => [:create, :reboot, :stop, :start, :destroy]
- }
-
- def name
- self.class.name.split('::').last.gsub('Driver', '').downcase
- end
-
- def self.exceptions(&block)
- ExceptionHandler::exceptions(&block)
- end
-
- def self.define_hardware_profile(name,&block)
- @hardware_profiles ||= []
- hw_profile = @hardware_profiles.find{|e| e.name == name}
- return if hw_profile
- hw_profile = ::Deltacloud::HardwareProfile.new( name, &block )
- @hardware_profiles << hw_profile
- hw_params = hw_profile.params
- unless hw_params.empty?
- feature :instances, :hardware_profiles do
- decl.operation(:create) { add_params(hw_params) }
- end
- end
- end
-
- def self.hardware_profiles
- @hardware_profiles ||= []
- @hardware_profiles
- end
-
- def hardware_profiles(credentials, opts = nil)
- results = self.class.hardware_profiles
- filter_hardware_profiles(results, opts)
- end
-
- def hardware_profile(credentials, name)
- hardware_profiles(credentials, :id => name).first
- end
-
- def filter_hardware_profiles(profiles, opts)
- if opts
- if v = opts[:architecture]
- profiles = profiles.select { |hwp| hwp.include?(:architecture, v) }
- end
- # As a request param, we call 'name' 'id'
- if v = opts[:id]
- profiles = profiles.select { |hwp| hwp.name == v }
- end
- end
- profiles
- end
-
- def find_hardware_profile(credentials, name, image_id)
- hwp = nil
- if name
- unless hwp = hardware_profiles(credentials, :id => name).first
- raise BackendError.new(400, "bad-hardware-profile-name",
- "Hardware profile '#{name}' does not exist", nil)
- end
- else
- unless image = image(credentials, :id=>image_id)
- raise BackendError.new(400, "bad-image-id",
- "Image with ID '#{image_id}' does not exist", nil)
- end
- hwp = hardware_profiles(credentials,
- :architecture=>image.architecture).first
- end
- return hwp
- end
-
- def self.define_instance_states(&block)
- machine = ::Deltacloud::StateMachine.new(STATE_MACHINE_OPTS, &block)
- @instance_state_machine = machine
- end
-
- def self.instance_state_machine
- @instance_state_machine
- end
-
- def instance_state_machine
- self.class.instance_state_machine
- end
-
- def instance_actions_for(state)
- actions = []
- state_key = state.downcase.to_sym
- states = instance_state_machine.states()
- current_state = states.find{|e| e.name == state.underscore.to_sym }
- if ( current_state )
- actions = current_state.transitions.collect{|e|e.action}
- actions.reject!{|e| e.nil?}
- end
- actions
- end
-
- ## Capabilities
- # The rabbit dsl supports declaring a capability that is required
- # in the backend driver for the call to succeed. A driver can
- # provide a capability by implementing the method with the same
- # name as the capability. Below is a list of the capabilities as
- # the expected method signatures.
- #
- # Following the capability list are the resource member show
- # methods. They each require that the corresponding collection
- # method be defined
- #
- # TODO: standardize all of these to the same signature (credentials, opts)
- #
- # def realms(credentials, opts=nil)
- #
- # def images(credentials, ops)
- #
- # def instances(credentials, ops)
- # def create_instance(credentials, image_id, opts)
- # def start_instance(credentials, id)
- # def stop_instance(credentials, id)
- # def reboot_instance(credentials, id)
- #
- # def storage_volumes(credentials, ops)
- #
- # def storage_snapshots(credentials, ops)
- #
- # def buckets(credentials, opts = nil)
- # def create_bucket(credentials, name, opts=nil)
- # def delete_bucket(credentials, name, opts=nil)
- #
- # def blobs(credentials, opts = nil)
- # def blob_data(credentials, bucket_id, blob_id, opts)
- # def create_blob(credentials, bucket_id, blob_id, blob_data, opts=nil)
- # def delete_blob(credentials, bucket_id, blob_id, opts=nil)
- #
- # def keys(credentials, opts)
- # def create_key(credentials, opts)
- # def destroy_key(credentials, opts)
- #
- # def firewalls(credentials, opts)
- # def create_firewall(credentials, opts)
- # def delete_firewall(credentials, opts)
- # def create_firewall_rule(credentials, opts)
- # def delete_firewall_rule(credentials, opts)
- # def providers(credentials)
- def realm(credentials, opts)
- realms = realms(credentials, opts).first if has_capability?(:realms)
- end
-
- def image(credentials, opts)
- images(credentials, opts).first if has_capability?(:images)
- end
-
- def instance(credentials, opts)
- instances(credentials, opts).first if has_capability?(:instances)
- end
-
- def storage_volume(credentials, opts)
- storage_volumes(credentials, opts).first if has_capability?(:storage_volumes)
- end
-
- def storage_snapshot(credentials, opts)
- storage_snapshots(credentials, opts).first if has_capability?(:storage_snapshots)
- end
-
- def bucket(credentials, opts = {})
- #list of objects within bucket
- buckets(credentials, opts).first if has_capability?(:buckets)
- end
-
- def blob(credentials, opts = {})
- blobs(credentials, opts).first if has_capability?(:blobs)
- end
-
- def key(credentials, opts=nil)
- keys(credentials, opts).first if has_capability?(:keys)
- end
-
- def firewall(credentials, opts={})
- firewalls(credentials, opts).first if has_capability?(:firewalls)
- end
-
- MEMBER_SHOW_METHODS =
- [ :realm, :image, :instance, :storage_volume, :bucket, :blob, :key, :firewall ]
-
- def has_capability?(capability)
- if MEMBER_SHOW_METHODS.include?(capability.to_sym)
- has_capability?(capability.to_s.pluralize)
- else
- respond_to?(capability)
- end
- end
-
- def filter_on(collection, attribute, opts)
- return collection if opts.nil?
- return collection if opts[attribute].nil?
- filter = opts[attribute]
- if ( filter.is_a?( Array ) )
- return collection.select{|e| filter.include?( e.send(attribute) ) }
- else
- return collection.select{|e| filter == e.send(attribute) }
- end
- end
-
- def supported_collections
- DEFAULT_COLLECTIONS
- end
-
- def has_collection?(collection)
- supported_collections.include?(collection)
- end
-
- def catched_exceptions_list
- { :error => [], :auth => [], :glob => [] }
- end
-
- def api_provider
- Thread.current[:provider] || ENV['API_PROVIDER']
- end
-
- # Return an array of the providers statically configured
- # in the driver's YAML file
- def configured_providers
- []
- end
- end
-
-end
diff --git a/server/lib/deltacloud/base_driver/exceptions.rb b/server/lib/deltacloud/base_driver/exceptions.rb
deleted file mode 100644
index a89b05f..0000000
--- a/server/lib/deltacloud/base_driver/exceptions.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-
-module Deltacloud
- module ExceptionHandler
-
- class DeltacloudException < StandardError
-
- attr_accessor :code, :name, :message, :backtrace, :request
-
- def initialize(code, name, message, backtrace, request=nil)
- @code, @name, @message = code, name, message
- @backtrace = backtrace
- @request = request
- self
- end
-
- end
-
- class AuthenticationFailure < DeltacloudException
- def initialize(e, message=nil)
- message ||= e.message
- super(401, e.class.name, message, e.backtrace)
- end
- end
-
- class UnknownMediaTypeError < DeltacloudException
- def initialize(e, message=nil)
- message ||= e.message
- super(406, e.class.name, message, e.backtrace)
- end
- end
-
- class MethodNotAllowed < DeltacloudException
- def initialize(e, message=nil)
- message ||= e.message
- super(405, e.class.name, message, e.backtrace)
- end
- end
-
- class ValidationFailure < DeltacloudException
- def initialize(e, message=nil)
- message ||= e.message
- super(400, e.class.name, message, e.backtrace)
- end
- end
-
- class BackendError < DeltacloudException
- def initialize(e, message=nil)
- message ||= e.message
- super(500, e.class.name, message, e.backtrace, message)
- end
- end
-
- class ProviderError < DeltacloudException
- def initialize(e, message)
- message ||= e.message
- super(502, e.class.name, message, e.backtrace)
- end
- end
-
- class ProviderTimeout < DeltacloudException
- def initialize(e, message)
- message ||= e.message
- super(504, e.class.name, message, e.backtrace)
- end
- end
-
- class NotImplemented < DeltacloudException
- def initialize(e, message)
- message ||= e.message
- super(501, e.class.name, message, e.backtrace)
- end
- end
-
- class ObjectNotFound < DeltacloudException
- def initialize(e, message)
- message ||= e.message
- super(404, e.class.name, message, e.backtrace)
- end
- end
-
- class NotSupported < DeltacloudException
- def initialize(message)
- super(501, self.class.name, message, self.backtrace)
- end
- end
-
- class ExceptionDef
- attr_accessor :status
- attr_accessor :message
- attr_reader :conditions
- attr_reader :handler
-
- def initialize(conditions, &block)
- @conditions = conditions
- instance_eval(&block) if block_given?
- end
-
- def status(code)
- self.status = code
- end
-
- def message(message)
- self.message = message
- end
-
- def exception(handler)
- self.handler = handler
- end
-
- # Condition can be class or regexp
- #
- def match?(e)
- @conditions.each do |c|
- return true if c.class == Class && e.class == c
- return true if c.class == Regexp && (e.class.name =~ c or e.message =~ c)
- end
- return false
- end
-
- def handler(e)
- return @handler if @handler
- case @status
- when 401 then Deltacloud::ExceptionHandler::AuthenticationFailure.new(e, @message)
- when 404 then Deltacloud::ExceptionHandler::ObjectNotFound.new(e, @message)
- when 406 then Deltacloud::ExceptionHandler::UnknownMediaTypeError.new(e, @message)
- when 405 then Deltacloud::ExceptionHandler::MethodNotAllowed.new(e, @message)
- when 400 then Deltacloud::ExceptionHandler::ValidationFailure.new(e, @message)
- when 500 then Deltacloud::ExceptionHandler::BackendError.new(e, @message)
- when 501 then Deltacloud::ExceptionHandler::NotImplemented.new(e, @message)
- when 502 then Deltacloud::ExceptionHandler::ProviderError.new(e, @message)
- when 504 then Deltacloud::ExceptionHandler::ProviderTimeout.new(e, @message)
- end
- end
-
- end
-
- class Exceptions
- attr_reader :exception_definitions
-
- def initialize(&block)
- @exception_definitions = []
- instance_eval(&block) if block_given?
- self
- end
-
- def on(*conditions, &block)
- @exception_definitions << ExceptionDef::new(conditions, &block) if block_given?
- end
- end
-
- def self.exceptions(&block)
- @definitions = Exceptions.new(&block).exception_definitions if block_given?
- @definitions
- end
-
- def safely(&block)
- begin
- block.call
- rescue
- report_method = $stderr.respond_to?(:err) ? :err : :puts
- Deltacloud::ExceptionHandler::exceptions.each do |exdef|
- if exdef.match?($!)
- new_exception = exdef.handler($!)
- m = new_exception.message.nil? ? $!.message : new_exception.message
- $stderr.send(report_method, "#{[$!.class.to_s, m].join(':')}\n#{$!.backtrace[0..10].join("\n")}")
- raise exdef.handler($!) unless new_exception.nil?
- end
- end
- $stderr.send(report_method, "[NO HANDLED] #{[$!.class.to_s, $!.message].join(': ')}\n#{$!.backtrace.join("\n")}")
- raise Deltacloud::ExceptionHandler::BackendError.new($!, "Unhandled exception or status code (#{$!.message})")
- end
- end
-
- end
-
-end
diff --git a/server/lib/deltacloud/base_driver/features.rb b/server/lib/deltacloud/base_driver/features.rb
deleted file mode 100644
index 37e5ef0..0000000
--- a/server/lib/deltacloud/base_driver/features.rb
+++ /dev/null
@@ -1,276 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-require 'deltacloud/validation'
-
-# Add advertising of optional features to the base driver
-module Deltacloud
-
- class FeatureError < StandardError; end
- class DuplicateFeatureDeclError < FeatureError; end
- class UndeclaredFeatureError < FeatureError; end
-
- class BaseDriver
-
- # An operation on a collection like cretae or show. Features
- # can add parameters to operations
- class Operation
- attr_reader :name
-
- include Deltacloud::Validation
-
- def initialize(name, &block)
- @name = name
- @params = {}
- instance_eval &block
- end
- end
-
- # The declaration of a feature, defines what operations
- # are modified by it
- class FeatureDecl
- attr_reader :name, :operations
-
- def initialize(name, &block)
- @name = name
- @operations = []
- instance_eval &block
- end
-
- def description(text=nil)
- @description = text if text
- @description
- end
-
- # Add/modify an operation or look up an existing one. If +block+ is
- # provided, create a new operation if none exists with name
- # +name+. Evaluate the +block+ against this instance. If no +block+
- # is provided, look up the operation with name +name+
- def operation(name, &block)
- op = @operations.find { |op| op.name == name }
- if block_given?
- if op.nil?
- op = Operation.new(name, &block)
- @operations << op
- else
- op.instance_eval(&block)
- end
- end
- op
- end
- end
-
- # A specific feature enabled by a driver (see +feature+)
- class Feature
- attr_reader :decl, :constraints
-
- def initialize(decl, &block)
- @decl = decl
- @constraints = {}
- instance_eval &block if block_given?
- end
-
- def name
- decl.name
- end
-
- def operations
- decl.operations
- end
-
- def description
- decl.description
- end
-
- def constraint(name, value)
- @constraints[name] = value
- end
- end
-
- def self.feature_decls
- @@feature_decls ||= {}
- end
-
- def self.feature_decl_for(collection, name)
- decls = feature_decls[collection]
- if decls
- decls.find { |dcl| dcl.name == name }
- else
- nil
- end
- end
-
- # Declare a new feature
- def self.declare_feature(collection, name, &block)
- feature_decls[collection] ||= []
- raise DuplicateFeatureDeclError if feature_decl_for(collection, name)
- feature_decls[collection] << FeatureDecl.new(name, &block)
- end
-
- def self.features
- @features ||= {}
- end
-
- # Declare in a driver that it supports a specific feature
- #
- # The same feature can be declared multiple times in a driver, so that
- # it can be changed successively by passing in different blocks.
- def self.feature(collection, name, &block)
- features[collection] ||= []
- if f = features[collection].find { |f| f.name == name }
- f.instance_eval(&block) if block_given?
- return f
- end
- unless decl = feature_decl_for(collection, name)
- raise UndeclaredFeatureError, "No feature #{name} for #{collection}"
- end
- features[collection] << Feature.new(decl, &block)
- end
-
- def features(collection)
- self.class.features[collection] || []
- end
-
- def features_for_operation(collection, operation)
- features(collection).select do |f|
- f.operations.detect { |o| o.name == operation }
- end
- end
-
- #
- # Declaration of optional features
- #
- declare_feature :images, :owner_id do
- description "Filter images using owner id"
- operation :index do
- param :owner_id, :string, :optional, [], "Owner ID"
- end
- end
-
- declare_feature :images, :user_name do
- description "Allow specifying user name for created image"
- operation :create do
- param :name, :string, :optional, [], "Image name"
- end
- end
-
- declare_feature :images, :user_description do
- description "Allow specifying user description for created image"
- operation :create do
- param :description, :string, :optional, [], "Image description"
- end
- end
-
- declare_feature :instances, :user_name do
- description "Accept a user-defined name on instance creation"
- operation :create do
- param :name, :string, :optional, [], "The user-defined name"
- end
- end
-
- declare_feature :instances, :user_data do
- description "Make user-defined data available on a special webserver"
- operation :create do
- param :user_data, :string, :optional, [],
- "Base64 encoded user data will be published to internal webserver"
- end
- end
-
- declare_feature :instances, :user_iso do
- description "Make user-defined ISO available inside instance"
- operation :create do
- param :user_iso, :string, :optional, [],
- "Base64 encoded gzipped ISO file will be accessible as CD-ROM drive in instance"
- end
- end
-
- declare_feature :instances, :user_files do
- description "Accept up to 5 files to be placed into the instance before launch."
- operation :create do
- 1.upto(5) do |i|
- param :"path#{i}", :string, :optional, [],
- "Path where to place the #{i.ordinalize} file, up to 255 characters"
- param :"content#{i}", :string, :optional, nil,
- "Contents for the #{i.ordinalize} file, up to 10 kB, Base64 encoded"
- end
- end
- end
-
- declare_feature :instances, :firewalls do
- description "Put instance in one or more firewalls (security groups) on launch"
- operation :create do
- param :firewalls, :array, :optional, nil, "Array of firewall ID strings"
- "Array of firewall (security group) id"
- end
- end
-
- declare_feature :instances, :authentication_key do
- operation :create do
- param :keyname, :string, :optional, [], "Key authentification method"
- end
- operation :show do
- end
- end
-
- declare_feature :instances, :authentication_password do
- operation :create do
- param :password, :string, :optional
- end
- end
-
- declare_feature :instances, :hardware_profiles do
- description "Size instances according to changes to a hardware profile"
- # The parameters are filled in from the hardware profiles
- end
-
- declare_feature :buckets, :bucket_location do
- description "Take extra location parameter for Bucket creation (e.g. S3, 'eu' or 'us-west-1')"
- operation :create do
- param :location, :string, :optional
- end
- end
-
- declare_feature :instances, :register_to_load_balancer do
- description "Register instance to load balancer"
- operation :create do
- param :load_balancer_id, :string, :optional
- end
- end
-
- declare_feature :instances, :instance_count do
- description "Number of instances to be launch with at once"
- operation :create do
- param :instance_count, :string, :optional
- end
- end
-
- declare_feature :instances, :attach_snapshot do
- description "Attach an snapshot to instance on create"
- operation :create do
- param :snapshot_id, :string, :optional
- param :device_name, :string, :optional
- end
- end
-
- declare_feature :instances, :sandboxing do
- description "Allow lanuching sandbox images"
- operation :create do
- param :sandbox, :string, :optional
- end
- end
-
- end
-end
diff --git a/server/lib/deltacloud/collections.rb b/server/lib/deltacloud/collections.rb
new file mode 100644
index 0000000..2363887
--- /dev/null
+++ b/server/lib/deltacloud/collections.rb
@@ -0,0 +1,54 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud
+
+ def self.collection_names
+ @collections.map { |c| c.collection_name }
+ end
+
+ def self.collections
+ @collections ||= []
+ end
+
+ module Collections
+
+ def self.collection(name)
+ Deltacloud.collections.find { |c| c.collection_name == name }
+ end
+
+ def self.deltacloud_modules
+ @deltacloud_modules ||= []
+ end
+
+ Dir[File.join(File::dirname(__FILE__), "collections", "*.rb")].each do |collection|
+ require collection
+ base_collection_name = File.basename(collection).gsub('.rb', '')
+ deltacloud_module_class = Deltacloud::Collections.const_get(base_collection_name.camelize)
+ deltacloud_modules << deltacloud_module_class
+ deltacloud_module_class.collections.each do |c|
+ Deltacloud.collections << c
+ end unless deltacloud_module_class.collections.nil?
+ end
+
+ def self.included(klass)
+ klass.class_eval do
+ Deltacloud::Collections.deltacloud_modules.each { |c| use c }
+ end
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/addresses.rb b/server/lib/deltacloud/collections/addresses.rb
new file mode 100644
index 0000000..b97d170
--- /dev/null
+++ b/server/lib/deltacloud/collections/addresses.rb
@@ -0,0 +1,83 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Addresses < Base
+
+ check_capability :for => lambda { |m| driver.respond_to? m }
+
+ collection :addresses do
+ description "Pool of IP addresses allocated in cloud provider"
+
+ standard_index_operation
+ standard_show_operation
+
+ operation :create, :with_capability => :create_address do
+ description "Acquire a new IP address for use with your account."
+ control do
+ @address = driver.create_address(credentials, {})
+ status 201 # Created
+ response['Location'] = address_url(@address.id)
+ respond_to do |format|
+ format.xml { haml :"addresses/show", :ugly => true }
+ format.html { haml :"addresses/_address", :layout => false }
+ format.json { convert_to_json(:address, @address) }
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :destroy_address do
+ control do
+ driver.destroy_address(credentials, { :id => params[:id]})
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(addresses_url) }
+ end
+ end
+ end
+
+ action :associate, :with_capability => :associate_address do
+ description "Associate an IP address to an instance"
+ param :instance_id, :string, :required
+ control do
+ driver.associate_address(credentials, { :id => params[:id], :instance_id => params[:instance_id]})
+ status 202 # Accepted
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(address_url(params[:id])) }
+ end
+ end
+ end
+
+ action :disassociate, :with_capability => :associate_address do
+ description "Disassociate an IP address from an instance"
+ control do
+ driver.disassociate_address(credentials, { :id => params[:id] })
+ status 202 # Accepted
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(address_url(params[:id])) }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/buckets.rb b/server/lib/deltacloud/collections/buckets.rb
new file mode 100644
index 0000000..044bd6a
--- /dev/null
+++ b/server/lib/deltacloud/collections/buckets.rb
@@ -0,0 +1,215 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Buckets < Base
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ collection :buckets do
+
+ collection :blobs, :with_id => :blob_id, :no_member => true do
+
+ operation :show, :with_capability => :blob do
+ control do
+ @blob = driver.blob(credentials, { :id => params[:blob_id], 'bucket' => params[:id]} )
+ if @blob
+ respond_to do |format|
+ format.xml { haml :"blobs/show" }
+ format.html { haml :"blobs/show" }
+ format.json { convert_to_json(:blob, @blob) }
+ end
+ else
+ report_error(404)
+ end
+ end
+
+ end
+
+ operation :create, :with_capability => :create_blob do
+ description "Create new blob"
+ param :blob_id, :string, :required
+ param :blob_data, :hash, :required
+ control do
+ bucket_id = params[:id]
+ blob_id = params['blob_id']
+ blob_data = params['blob_data']
+ user_meta = {}
+ #metadata from params (i.e., passed by http form post, e.g. browser)
+ max = params[:meta_params]
+ if(max)
+ (1..max.to_i).each do |i|
+ key = params[:"meta_name#{i}"]
+ key = "HTTP_X_Deltacloud_Blobmeta_#{key}"
+ value = params[:"meta_value#{i}"]
+ user_meta[key] = value
+ end
+ end
+ @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
+ respond_to do |format|
+ format.xml { haml :"blobs/show" }
+ format.html { haml :"blobs/show"}
+ format.json {convert_to_json(:blob, @blob)}
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :delete_blob do
+ control do
+ bucket_id = params[:id]
+ blob_id = params[:blob_id]
+ driver.delete_blob(credentials, bucket_id, blob_id)
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(bucket_url(bucket_id)) }
+ end
+ end
+ end
+
+ action :stream, :http_method => :put, :with_capability => :create_blob do
+ description "Stream new blob data into the blob"
+ control do
+ if(env["BLOB_SUCCESS"]) #ie got a 200ok after putting blob
+ content_type = env["CONTENT_TYPE"]
+ content_type ||= ""
+ @blob = driver.blob(credentials, {:id => params[:blob],
+ 'bucket' => params[:bucket]})
+ respond_to do |format|
+ format.xml { haml :"blobs/show" }
+ format.html { haml :"blobs/show" }
+ format.json { convert_to_json(:blob, @blob) }
+ end
+ elsif(env["BLOB_FAIL"])
+ report_error(500) #OK?
+ else # small blobs - < 112kb dont hit the streaming monkey patch - use 'normal' create_blob
+ # also, if running under webrick don't hit the streaming patch (Thin specific)
+ bucket_id = params[:bucket]
+ blob_id = params[:blob]
+ temp_file = Tempfile.new("temp_blob_file")
+ temp_file.write(env['rack.input'].read)
+ temp_file.flush
+ content_type = env['CONTENT_TYPE'] || ""
+ blob_data = {:tempfile => temp_file, :type => content_type}
+ user_meta = BlobHelper::extract_blob_metadata_hash(request.env)
+ @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
+ temp_file.delete
+ respond_to do |format|
+ format.xml { haml :"blobs/show" }
+ format.html { haml :"blobs/show" }
+ format.json { convert_to_json(:blob, @blob) }
+ end
+ end
+ end
+ end
+
+ action :metadata, :http_method => :head, :with_capability => :blob_metadata do
+ control do
+ @blob_id = params[:blob]
+ @blob_metadata = driver.blob_metadata(credentials, {:id => params[:blob], 'bucket' => params[:bucket]})
+ if @blob_metadata
+ @blob_metadata.each do |k,v|
+ headers["X-Deltacloud-Blobmeta-#{k}"] = v
+ end
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ end
+ else
+ report_error(404)
+ end
+ end
+ end
+
+ action :update, :http_method => :post, :with_capability => :update_blob_metadata do
+ control do
+ meta_hash = BlobHelper::extract_blob_metadata_hash(request.env)
+ success = driver.update_blob_metadata(credentials, {'bucket'=>params[:bucket], :id =>params[:blob], 'meta_hash' => meta_hash})
+ if(success)
+ meta_hash.each do |k,v|
+ headers["X-Deltacloud-Blobmeta-#{k}"] = v
+ end
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ end
+ else
+ report_error(404) #FIXME is this the right error code?
+ end
+ end
+ end
+
+ action :content, :http_method => :get, :with_capability => :blob do
+ description "Download blob content"
+ control do
+ @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
+ if @blob
+ params['content_length'] = @blob.content_length
+ params['content_type'] = @blob.content_type
+ params['content_disposition'] = "attachment; filename=#{@blob.id}"
+ BlobStream.call(env, credentials, params)
+ else
+ report_error(404)
+ end
+ end
+ end
+
+ end
+
+ get route_for('/buckets/new') do
+ respond_to do |format|
+ format.html { haml :"buckets/new" }
+ end
+ end
+
+ standard_show_operation
+ standard_index_operation
+
+ operation :create, :with_capability => :create_bucket do
+ param :name, :string, :required
+ control do
+ @bucket = driver.create_bucket(credentials, params[:name], params)
+ status 201
+ response['Location'] = bucket_url(@bucket.id)
+ respond_to do |format|
+ format.xml { haml :"buckets/show" }
+ format.json { convert_to_json(:bucket, @bucket) }
+ format.html do
+ redirect bucket_url(@bucket.id) if @bucket and @bucket.id
+ redirect buckets_url
+ end
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :delete_bucket do
+ control do
+ driver.delete_bucket(credentials, params[:id], params)
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(buckets_url) }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/drivers.rb b/server/lib/deltacloud/collections/drivers.rb
new file mode 100644
index 0000000..41f324a
--- /dev/null
+++ b/server/lib/deltacloud/collections/drivers.rb
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Drivers < Base
+
+ collection :drivers do
+
+ operation :index do
+ control do
+ @drivers = Deltacloud::Drivers.driver_config
+ respond_to do |format|
+ format.xml { haml :"drivers/index" }
+ format.json { @drivers.to_json }
+ format.html { haml :"drivers/index" }
+ end
+ end
+ end
+
+ operation :show do
+ control do
+ @name = params[:id].to_sym
+ if driver_symbol == @name
+ @providers = driver.providers(credentials) if driver.respond_to? :providers
+ end
+ @driver = Deltacloud::Drivers.driver_config[@name]
+ halt 404 unless @driver
+ respond_to do |format|
+ format.xml { haml :"drivers/show" }
+ format.json { @driver.to_json }
+ format.html { haml :"drivers/show" }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/firewalls.rb b/server/lib/deltacloud/collections/firewalls.rb
new file mode 100644
index 0000000..0a4242a
--- /dev/null
+++ b/server/lib/deltacloud/collections/firewalls.rb
@@ -0,0 +1,116 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Firewalls < Base
+
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ get route_for('/firewalls/:id/new_rule') do
+ @firewall_name = params[:id]
+ respond_to do |format|
+ format.html {haml :"firewalls/new_rule" }
+ end
+ end
+
+ new_route_for :firewalls
+
+ collection :firewalls do
+ description "Allow user to define firewall rules for an instance (ec2 security groups) eg expose ssh access [port 22, tcp]."
+
+ collection :rules, :with_id => :rule_id, :no_member => true do
+
+ operation :destroy, :with_capability => :delete_firewall_rule do
+ control do
+ opts = {}
+ opts[:firewall] = params[:id]
+ opts[:rule_id] = params[:rule_id]
+ driver.delete_firewall_rule(credentials, opts)
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html {redirect firewall_url(params[:id])}
+ end
+ end
+ end
+
+ end
+
+ standard_show_operation
+ standard_index_operation
+
+ operation :create, :with_capability => :create_firewall do
+ param :name, :string, :required
+ param :description, :string, :required
+ control do
+ @firewall = driver.create_firewall(credentials, params )
+ status 201 # Created
+ response['Location'] = firewall_url(@firewall.id)
+ respond_to do |format|
+ format.xml { haml :"firewalls/show" }
+ format.html { haml :"firewalls/show" }
+ format.json { convert_to_json(:firewall, @firewall) }
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :delete_firewall do
+ control do
+ driver.delete_firewall(credentials, params)
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(firewalls_url) }
+ end
+ end
+ end
+
+ action :rules, :with_capability => :create_firewall_rule do
+ param :protocol, :required, :string, ['tcp','udp','icmp'], "Transport layer protocol for the rule"
+ param :port_from, :required, :string, [], "Start of port range for the rule"
+ param :port_to, :required, :string, [], "End of port range for the rule"
+ control do
+ #source IPs from params
+ addresses = params.inject([]){|result,current| result << current.last unless current.grep(/^ip[-_]address/i).empty?; result}
+ #source groups from params
+ groups = {}
+ max_groups = params.select{|k,v| k=~/^group/}.size/2
+ for i in (1..max_groups) do
+ groups.merge!({params["group#{i}"]=>params["group#{i}owner"]})
+ end
+ params['addresses'] = addresses
+ params['groups'] = groups
+ if addresses.empty? && groups.empty?
+ raise Deltacloud::ExceptionHandler::ValidationFailure.new(
+ StandardError.new("No sources. Specify at least one source ip_address or group")
+ )
+ end
+ driver.create_firewall_rule(credentials, params)
+ @firewall = driver.firewall(credentials, {:id => params[:id]})
+ status 201
+ respond_to do |format|
+ format.xml { haml :"firewalls/show" }
+ format.html { haml :"firewalls/show" }
+ format.json { convert_to_json(:firewall, @firewall) }
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/server/lib/deltacloud/collections/hardware_profiles.rb b/server/lib/deltacloud/collections/hardware_profiles.rb
new file mode 100644
index 0000000..ff01d4a
--- /dev/null
+++ b/server/lib/deltacloud/collections/hardware_profiles.rb
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class HardwareProfiles < Base
+
+ collection :hardware_profiles do
+
+ standard_index_operation
+ standard_show_operation
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/images.rb b/server/lib/deltacloud/collections/images.rb
new file mode 100644
index 0000000..c8b3e08
--- /dev/null
+++ b/server/lib/deltacloud/collections/images.rb
@@ -0,0 +1,70 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Images < Base
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ new_route_for :images do
+ @instance = Instance.new( :id => params[:instance_id] )
+ status 404 unless @instance
+ end
+
+ collection :images do
+ description "Within a cloud provider a realm represents a boundary containing resources"
+
+ operation :index, :with_capability => :images do
+ param :architecture, :string, :optional
+ control { filter_all(:images) }
+ end
+
+ operation :show, :with_capability => :image do
+ control { show(:image) }
+ end
+
+ operation :create, :with_capability => :create_image do
+ param :instance_id, :string, :required
+ control do
+ @image = driver.create_image(credentials, {
+ :id => params[:instance_id],
+ :name => params[:name],
+ :description => params[:description]
+ })
+ status 201 # Created
+ response['Location'] = image_url(@image.id)
+ respond_to do |format|
+ format.xml { haml :"images/show" }
+ format.json { xml_to_json('images/show') }
+ format.html { haml :"images/show" }
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :destroy_image do
+ control do
+ driver.destroy_image(credentials, params[:id])
+ respond_to do |format|
+ format.xml { status 204 }
+ format.json { status 204 }
+ format.html { redirect(images_url) }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/instance_states.rb b/server/lib/deltacloud/collections/instance_states.rb
new file mode 100644
index 0000000..56c1561
--- /dev/null
+++ b/server/lib/deltacloud/collections/instance_states.rb
@@ -0,0 +1,57 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class InstanceStates < Base
+
+ collection :instance_states do
+ operation :index do
+ control do
+ @machine = driver.instance_state_machine
+ respond_to do |format|
+ format.xml { haml :'instance_states/show', :layout => false }
+ format.json do
+ out = []
+ @machine.states.each do |state|
+ transitions = state.transitions.collect do |t|
+ t.automatically? ? {:to => t.destination, :auto => 'true'} : {:to => t.destination, :action => t.action}
+ end
+ out << { :name => state, :transitions => transitions }
+ end
+ out.to_json
+ end
+ format.html { haml :'instance_states/show'}
+ format.gv { erb :"instance_states/show" }
+ format.png do
+ # Trick respond_to into looking up the right template for the
+ # graphviz file
+ gv = erb(:"instance_states/show")
+ png = ''
+ cmd = 'dot -Kdot -Gpad="0.2,0.2" -Gsize="5.0,8.0" -Gdpi="180" -Tpng'
+ Open3.popen3( cmd ) do |stdin, stdout, stderr|
+ stdin.write( gv )
+ stdin.close()
+ png = stdout.read
+ end
+ content_type 'image/png'
+ png
+ end
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/instances.rb b/server/lib/deltacloud/collections/instances.rb
new file mode 100644
index 0000000..5202149
--- /dev/null
+++ b/server/lib/deltacloud/collections/instances.rb
@@ -0,0 +1,103 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Instances < Base
+
+ include Deltacloud::InstanceFeatures
+
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ new_route_for(:instances) do
+ @instance = Instance.new( { :id=>params[:id], :image_id=>params[:image_id] } )
+ @image = Image.new( :id => params[:image_id] )
+ @hardware_profiles = driver.hardware_profiles(credentials, :architecture => @image.architecture )
+ @realms = [Realm.new(:id => params[:realm_id])] if params[:realm_id]
+ @realms ||= driver.realms(credentials)
+ end
+
+ collection :instances do
+
+ standard_show_operation
+ standard_index_operation
+
+ operation :create, :with_capability => :create_instance do
+ param :image_id, :string, :required
+ param :realm_id, :string, :optional
+ param :hwp_id, :string, :optional
+ control do
+ @instance = driver.create_instance(credentials, params[:image_id], params)
+ if @instance.kind_of? Array
+ @elements = @instance
+ action_handler = "index"
+ else
+ response['Location'] = instance_url(@instance.id)
+ action_handler = "show"
+ end
+ status 201 # Created
+ respond_to do |format|
+ format.xml { haml :"instances/#{action_handler}" }
+ format.json { xml_to_json("instances/#{action_handler}") }
+ format.html do
+ if @elements
+ haml :"instances/index"
+ elsif @instance and @instance.id
+ response['Location'] = instance_url(@instance.id)
+ haml :"instances/show"
+ else
+ redirect instances_url
+ end
+ end
+ end
+ end
+ end
+
+ action :reboot, :with_capability => :reboot_instance do
+ description "Reboot a running instance."
+ control { instance_action(:reboot) }
+ end
+
+ action :start, :with_capability => :start_instance do
+ description "Start an instance."
+ control { instance_action(:start) }
+ end
+
+ action :stop, :with_capability => :stop_instance do
+ description "Stop a running instance."
+ control { instance_action(:stop) }
+ end
+
+ operation :destroy, :with_capability => :destroy_instance do
+ control { instance_action(:destroy) }
+ end
+
+ action :run, :with_capability => :run_instance do
+ param :id, :string, :required
+ param :cmd, :string, :required, [], "Shell command to run on instance"
+ param :private_key, :string, :optional, [], "Private key in PEM format for authentication"
+ param :password, :string, :optional, [], "Password used for authentication"
+ control do
+ @output = driver.run_on_instance(credentials, params)
+ respond_to do |format|
+ format.xml { haml :"instances/run" }
+ format.html { haml :"instances/run" }
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/keys.rb b/server/lib/deltacloud/collections/keys.rb
new file mode 100644
index 0000000..55d0fa8
--- /dev/null
+++ b/server/lib/deltacloud/collections/keys.rb
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Keys < Base
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ get route_for('/keys/new') do
+ respond_to do |format|
+ format.html { haml :"keys/new" }
+ end
+ end
+
+ collection :keys do
+
+ standard_show_operation
+ standard_index_operation
+
+ operation :create, :with_capability => :create_key do
+ param :name, :string, :required
+ control do
+ @key = driver.create_key(credentials, { :key_name => params[:name] })
+ status 201
+ response['Location'] = key_url(@key.id)
+ respond_to do |format|
+ format.xml { haml :"keys/show", :ugly => true }
+ format.html { haml :"keys/show" }
+ format.json { convert_to_json(:key, @key)}
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :destroy_key do
+ control do
+ driver.destroy_key(credentials, { :id => params[:id]})
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(keys_url) }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/load_balancers.rb b/server/lib/deltacloud/collections/load_balancers.rb
new file mode 100644
index 0000000..d093ead
--- /dev/null
+++ b/server/lib/deltacloud/collections/load_balancers.rb
@@ -0,0 +1,85 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class LoadBalancers < Base
+ check_capability :for => lambda { |m| driver.has_capability? m }
+
+ collection :load_balancers do
+ description "Load balancers are used distribute workload across multiple instances"
+
+ standard_index_operation
+ standard_show_operation
+
+ operation :create do
+ param :name, :string, :required
+ param :realm_id, :string, :required
+ param :listener_protocol, :string, :required, ['HTTP', 'TCP']
+ param :listener_balancer_port, :string, :required
+ param :listener_instance_port, :string, :required
+ control do
+ @load_balancer = driver.create_load_balancer(credentials, params)
+ status 201 # Created
+ response['Location'] = load_balancer_url(@instance.id)
+ respond_to do |format|
+ format.xml { haml :"load_balancers/show" }
+ format.json { convert_to_json(:load_balancer, @load_balancer) }
+ format.html { haml :"load_balancers/show" }
+ end
+ end
+ end
+
+ action :register do
+ param :instance_id, :string, :required
+ control do
+ driver.lb_register_instance(credentials, params)
+ @load_balancer = driver.load_balancer(credential, params[:id])
+ respond_to do |format|
+ format.xml { haml :'load_balancers/show' }
+ format.json ( xml_to_json('load_balancers/show'))
+ format.html { haml :'load_balancers/show' }
+ end
+ end
+ end
+
+ action :unregister do
+ param :instance_id, :string, :required
+ control do
+ driver.lb_unregister_instance(credentials, params)
+ @load_balancer = driver.load_balancer(credential, params[:id])
+ respond_to do |format|
+ format.xml { haml :'load_balancers/show' }
+ format.json ( xml_to_json('load_balancers/show'))
+ format.html { haml :'load_balancers/show' }
+ end
+ end
+ end
+
+ operation :destroy do
+ control do
+ driver.destroy_load_balancer(credentials, params[:id])
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(load_balancers_url) }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/realms.rb b/server/lib/deltacloud/collections/realms.rb
new file mode 100644
index 0000000..3f21625
--- /dev/null
+++ b/server/lib/deltacloud/collections/realms.rb
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class Realms < Base
+
+ collection :realms do
+ description "Within a cloud provider a realm represents a boundary containing resources"
+
+ standard_index_operation
+ standard_show_operation
+
+ end
+ end
+end
diff --git a/server/lib/deltacloud/collections/storage_snapshots.rb b/server/lib/deltacloud/collections/storage_snapshots.rb
new file mode 100644
index 0000000..b468614
--- /dev/null
+++ b/server/lib/deltacloud/collections/storage_snapshots.rb
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class StorageSnapshots < Base
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ new_route_for(:storage_snapshots)
+
+ collection :storage_snapshots do
+ standard_index_operation
+ standard_show_operation
+
+ operation :create, :with_capability => :create_storage_snapshot do
+ param :volume_id, :string, :required
+ control do
+ @storage_snapshot = driver.create_storage_snapshot(credentials, params)
+ status 201 # Created
+ response['Location'] = storage_snapshot_url(@storage_snapshot.id)
+ show(:storage_snapshot)
+ end
+ end
+
+ operation :destroy, :with_capability => :destroy_storage_snapshot do
+ control do
+ driver.destroy_storage_snapshot(credentials, params)
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(storage_snapshots_url) }
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/collections/storage_volumes.rb b/server/lib/deltacloud/collections/storage_volumes.rb
new file mode 100644
index 0000000..9cdcd66
--- /dev/null
+++ b/server/lib/deltacloud/collections/storage_volumes.rb
@@ -0,0 +1,99 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud::Collections
+ class StorageVolumes < Base
+
+ check_capability :for => lambda { |m| driver.respond_to? m }
+ check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) }
+
+ new_route_for(:storage_volumes)
+
+ get route_for("/storage_volumes/:id/attach_instance") do
+ @instances = driver.instances(credentials)
+ respond_to do |format|
+ format.html{ haml :"storage_volumes/attach"}
+ end
+ end
+
+ collection :storage_volumes do
+
+ standard_index_operation
+ standard_show_operation
+
+ operation :show, :with_capability => :storage_volume do
+ control { show(:storage_volume) }
+ end
+
+ operation :create do
+ param :snapshot_id, :string, :optional
+ param :capacity, :string, :optional
+ param :realm_id, :string, :optional
+ control do
+ @storage_volume = driver.create_storage_volume(credentials, params)
+ status 201
+ response['Location'] = storage_volume_url(@storage_volume.id)
+ respond_to do |format|
+ format.xml { haml :"storage_volumes/show" }
+ format.html { haml :"storage_volumes/show" }
+ format.json { convert_to_json(:storage_volume, @storage_volume) }
+ end
+ end
+ end
+
+ action :attach, :with_capability => :attach_storage_volume do
+ param :instance_id,:string, :required
+ param :device, :string, :required
+ control do
+ @storage_volume = driver.attach_storage_volume(credentials, params)
+ status 202
+ respond_to do |format|
+ format.html { redirect(storage_volume_url(params[:id]))}
+ format.xml { haml :"storage_volumes/show" }
+ format.json { convert_to_json(:storage_volume, @storage_volume) }
+ end
+ end
+ end
+
+ action :detach, :with_capability => :detach_storage_volume do
+ control do
+ volume = driver.storage_volume(credentials, :id => params[:id])
+ @storage_volume = driver.detach_storage_volume(credentials, :id => volume.id,
+ :instance_id => volume.instance_id,
+ :device => volume.device)
+ status 202
+ respond_to do |format|
+ format.html { redirect(storage_volume_url(params[:id]))}
+ format.xml { haml :"storage_volumes/show" }
+ format.json { convert_to_json(:storage_volume, @storage_volume) }
+ end
+ end
+ end
+
+ operation :destroy, :with_capability => :destroy_storage_volume do
+ control do
+ driver.destroy_storage_volume(credentials, params)
+ status 204
+ respond_to do |format|
+ format.xml
+ format.json
+ format.html { redirect(storage_volumes_url) }
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/server/lib/deltacloud/drivers/base_driver.rb b/server/lib/deltacloud/drivers/base_driver.rb
new file mode 100644
index 0000000..5fb1a79
--- /dev/null
+++ b/server/lib/deltacloud/drivers/base_driver.rb
@@ -0,0 +1,265 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud
+
+ class BaseDriver
+
+ include ExceptionHandler
+
+ def self.driver_name
+ name.split('::').last.gsub('Driver', '').downcase
+ end
+
+ def self.features
+ @features ||= []
+ end
+
+ def self.features_for(entity)
+ features.inject([]) do |result, item|
+ result << item[entity] if item.has_key? entity
+ result
+ end
+ end
+
+ def self.feature(collection, feature_name)
+ return if has_feature?(collection, feature_name)
+ features << { collection => feature_name }
+ end
+
+ def self.has_feature?(collection, feature_name)
+ features.any? { |f| (f.values.first == feature_name) && (f.keys.first == collection) }
+ end
+
+ def name
+ self.class.name.split('::').last.gsub('Driver', '').downcase
+ end
+
+ def self.exceptions(&block)
+ ExceptionHandler::exceptions(&block)
+ end
+
+ def self.define_hardware_profile(name,&block)
+ @hardware_profiles ||= []
+ hw_profile = @hardware_profiles.find{|e| e.name == name}
+ return if hw_profile
+ hw_profile = ::Deltacloud::HardwareProfile.new( name, &block )
+ @hardware_profiles << hw_profile
+ hw_params = hw_profile.params
+ # FIXME: Features
+ #unless hw_params.empty?
+ # feature :instances, :hardware_profiles do
+ # decl.operation(:create) { add_params(hw_params) }
+ # end
+ #end
+ end
+
+ def self.hardware_profiles
+ @hardware_profiles ||= []
+ @hardware_profiles
+ end
+
+ def hardware_profiles(credentials, opts = nil)
+ results = self.class.hardware_profiles
+ filter_hardware_profiles(results, opts)
+ end
+
+ def hardware_profile(credentials, name)
+ name = name[:id] if name.kind_of? Hash
+ hardware_profiles(credentials, :id => name).first
+ end
+
+ def filter_hardware_profiles(profiles, opts)
+ if opts
+ if v = opts[:architecture]
+ profiles = profiles.select { |hwp| hwp.include?(:architecture, v) }
+ end
+ # As a request param, we call 'name' 'id'
+ if v = opts[:id]
+ profiles = profiles.select { |hwp| hwp.name == v }
+ end
+ end
+ profiles
+ end
+
+ def find_hardware_profile(credentials, name, image_id)
+ hwp = nil
+ if name
+ unless hwp = hardware_profiles(credentials, :id => name).first
+ raise BackendError.new(400, "bad-hardware-profile-name",
+ "Hardware profile '#{name}' does not exist", nil)
+ end
+ else
+ unless image = image(credentials, :id=>image_id)
+ raise BackendError.new(400, "bad-image-id",
+ "Image with ID '#{image_id}' does not exist", nil)
+ end
+ hwp = hardware_profiles(credentials,
+ :architecture=>image.architecture).first
+ end
+ return hwp
+ end
+
+ def self.define_instance_states(&block)
+ machine = ::Deltacloud::StateMachine.new(&block)
+ @instance_state_machine = machine
+ end
+
+ def self.instance_state_machine
+ @instance_state_machine
+ end
+
+ def instance_state_machine
+ self.class.instance_state_machine
+ end
+
+ def instance_actions_for(state)
+ actions = []
+ state_key = state.downcase.to_sym
+ states = instance_state_machine.states()
+ current_state = states.find{|e| e.name == state.underscore.to_sym }
+ if ( current_state )
+ actions = current_state.transitions.collect{|e|e.action}
+ actions.reject!{|e| e.nil?}
+ end
+ actions
+ end
+
+ def has_capability?(method)
+ (self.class.instance_methods - self.class.superclass.methods).include? method
+ end
+
+ ## Capabilities
+ # The rabbit dsl supports declaring a capability that is required
+ # in the backend driver for the call to succeed. A driver can
+ # provide a capability by implementing the method with the same
+ # name as the capability. Below is a list of the capabilities as
+ # the expected method signatures.
+ #
+ # Following the capability list are the resource member show
+ # methods. They each require that the corresponding collection
+ # method be defined
+ #
+ # TODO: standardize all of these to the same signature (credentials, opts)
+ #
+ # def realms(credentials, opts=nil)
+ #
+ # def images(credentials, ops)
+ #
+ # def instances(credentials, ops)
+ # def create_instance(credentials, image_id, opts)
+ # def start_instance(credentials, id)
+ # def stop_instance(credentials, id)
+ # def reboot_instance(credentials, id)
+ #
+ # def storage_volumes(credentials, ops)
+ #
+ # def storage_snapshots(credentials, ops)
+ #
+ # def buckets(credentials, opts = nil)
+ # def create_bucket(credentials, name, opts=nil)
+ # def delete_bucket(credentials, name, opts=nil)
+ #
+ # def blobs(credentials, opts = nil)
+ # def blob_data(credentials, bucket_id, blob_id, opts)
+ # def create_blob(credentials, bucket_id, blob_id, blob_data, opts=nil)
+ # def delete_blob(credentials, bucket_id, blob_id, opts=nil)
+ #
+ # def keys(credentials, opts)
+ # def create_key(credentials, opts)
+ # def destroy_key(credentials, opts)
+ #
+ # def firewalls(credentials, opts)
+ # def create_firewall(credentials, opts)
+ # def delete_firewall(credentials, opts)
+ # def create_firewall_rule(credentials, opts)
+ # def delete_firewall_rule(credentials, opts)
+ # def providers(credentials)
+ def realm(credentials, opts)
+ realms = realms(credentials, opts).first if has_capability?(:realms)
+ end
+
+ def image(credentials, opts)
+ images(credentials, opts).first if has_capability?(:images)
+ end
+
+ def instance(credentials, opts)
+ instances(credentials, opts).first if has_capability?(:instances)
+ end
+
+ def storage_volume(credentials, opts)
+ storage_volumes(credentials, opts).first if has_capability?(:storage_volumes)
+ end
+
+ def storage_snapshot(credentials, opts)
+ storage_snapshots(credentials, opts).first if has_capability?(:storage_snapshots)
+ end
+
+ def bucket(credentials, opts = {})
+ #list of objects within bucket
+ buckets(credentials, opts).first if has_capability?(:buckets)
+ end
+
+ def blob(credentials, opts = {})
+ blobs(credentials, opts).first if has_capability?(:blobs)
+ end
+
+ def key(credentials, opts=nil)
+ keys(credentials, opts).first if has_capability?(:keys)
+ end
+
+ def firewall(credentials, opts={})
+ firewalls(credentials, opts).first if has_capability?(:firewalls)
+ end
+
+ MEMBER_SHOW_METHODS =
+ [ :realm, :image, :instance, :storage_volume, :bucket, :blob, :key, :firewall ]
+
+ def filter_on(collection, attribute, opts)
+ return collection if opts.nil?
+ return collection if opts[attribute].nil?
+ filter = opts[attribute]
+ if ( filter.is_a?( Array ) )
+ return collection.select{|e| filter.include?( e.send(attribute) ) }
+ else
+ return collection.select{|e| filter == e.send(attribute) }
+ end
+ end
+
+ def supported_collections
+ DEFAULT_COLLECTIONS
+ end
+
+ def has_collection?(collection)
+ supported_collections.include?(collection)
+ end
+
+ def catched_exceptions_list
+ { :error => [], :auth => [], :glob => [] }
+ end
+
+ def api_provider
+ Thread.current[:provider] || ENV['API_PROVIDER']
+ end
+
+ # Return an array of the providers statically configured
+ # in the driver's YAML file
+ def configured_providers
+ []
+ end
+ end
+
+end
diff --git a/server/lib/deltacloud/drivers/exceptions.rb b/server/lib/deltacloud/drivers/exceptions.rb
new file mode 100644
index 0000000..a89b05f
--- /dev/null
+++ b/server/lib/deltacloud/drivers/exceptions.rb
@@ -0,0 +1,191 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+module Deltacloud
+ module ExceptionHandler
+
+ class DeltacloudException < StandardError
+
+ attr_accessor :code, :name, :message, :backtrace, :request
+
+ def initialize(code, name, message, backtrace, request=nil)
+ @code, @name, @message = code, name, message
+ @backtrace = backtrace
+ @request = request
+ self
+ end
+
+ end
+
+ class AuthenticationFailure < DeltacloudException
+ def initialize(e, message=nil)
+ message ||= e.message
+ super(401, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class UnknownMediaTypeError < DeltacloudException
+ def initialize(e, message=nil)
+ message ||= e.message
+ super(406, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class MethodNotAllowed < DeltacloudException
+ def initialize(e, message=nil)
+ message ||= e.message
+ super(405, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class ValidationFailure < DeltacloudException
+ def initialize(e, message=nil)
+ message ||= e.message
+ super(400, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class BackendError < DeltacloudException
+ def initialize(e, message=nil)
+ message ||= e.message
+ super(500, e.class.name, message, e.backtrace, message)
+ end
+ end
+
+ class ProviderError < DeltacloudException
+ def initialize(e, message)
+ message ||= e.message
+ super(502, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class ProviderTimeout < DeltacloudException
+ def initialize(e, message)
+ message ||= e.message
+ super(504, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class NotImplemented < DeltacloudException
+ def initialize(e, message)
+ message ||= e.message
+ super(501, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class ObjectNotFound < DeltacloudException
+ def initialize(e, message)
+ message ||= e.message
+ super(404, e.class.name, message, e.backtrace)
+ end
+ end
+
+ class NotSupported < DeltacloudException
+ def initialize(message)
+ super(501, self.class.name, message, self.backtrace)
+ end
+ end
+
+ class ExceptionDef
+ attr_accessor :status
+ attr_accessor :message
+ attr_reader :conditions
+ attr_reader :handler
+
+ def initialize(conditions, &block)
+ @conditions = conditions
+ instance_eval(&block) if block_given?
+ end
+
+ def status(code)
+ self.status = code
+ end
+
+ def message(message)
+ self.message = message
+ end
+
+ def exception(handler)
+ self.handler = handler
+ end
+
+ # Condition can be class or regexp
+ #
+ def match?(e)
+ @conditions.each do |c|
+ return true if c.class == Class && e.class == c
+ return true if c.class == Regexp && (e.class.name =~ c or e.message =~ c)
+ end
+ return false
+ end
+
+ def handler(e)
+ return @handler if @handler
+ case @status
+ when 401 then Deltacloud::ExceptionHandler::AuthenticationFailure.new(e, @message)
+ when 404 then Deltacloud::ExceptionHandler::ObjectNotFound.new(e, @message)
+ when 406 then Deltacloud::ExceptionHandler::UnknownMediaTypeError.new(e, @message)
+ when 405 then Deltacloud::ExceptionHandler::MethodNotAllowed.new(e, @message)
+ when 400 then Deltacloud::ExceptionHandler::ValidationFailure.new(e, @message)
+ when 500 then Deltacloud::ExceptionHandler::BackendError.new(e, @message)
+ when 501 then Deltacloud::ExceptionHandler::NotImplemented.new(e, @message)
+ when 502 then Deltacloud::ExceptionHandler::ProviderError.new(e, @message)
+ when 504 then Deltacloud::ExceptionHandler::ProviderTimeout.new(e, @message)
+ end
+ end
+
+ end
+
+ class Exceptions
+ attr_reader :exception_definitions
+
+ def initialize(&block)
+ @exception_definitions = []
+ instance_eval(&block) if block_given?
+ self
+ end
+
+ def on(*conditions, &block)
+ @exception_definitions << ExceptionDef::new(conditions, &block) if block_given?
+ end
+ end
+
+ def self.exceptions(&block)
+ @definitions = Exceptions.new(&block).exception_definitions if block_given?
+ @definitions
+ end
+
+ def safely(&block)
+ begin
+ block.call
+ rescue
+ report_method = $stderr.respond_to?(:err) ? :err : :puts
+ Deltacloud::ExceptionHandler::exceptions.each do |exdef|
+ if exdef.match?($!)
+ new_exception = exdef.handler($!)
+ m = new_exception.message.nil? ? $!.message : new_exception.message
+ $stderr.send(report_method, "#{[$!.class.to_s, m].join(':')}\n#{$!.backtrace[0..10].join("\n")}")
+ raise exdef.handler($!) unless new_exception.nil?
+ end
+ end
+ $stderr.send(report_method, "[NO HANDLED] #{[$!.class.to_s, $!.message].join(': ')}\n#{$!.backtrace.join("\n")}")
+ raise Deltacloud::ExceptionHandler::BackendError.new($!, "Unhandled exception or status code (#{$!.message})")
+ end
+ end
+
+ end
+
+end
diff --git a/server/lib/deltacloud/helpers.rb b/server/lib/deltacloud/helpers.rb
index cf8531a..73f79ec 100644
--- a/server/lib/deltacloud/helpers.rb
+++ b/server/lib/deltacloud/helpers.rb
@@ -14,10 +14,84 @@
# License for the specific language governing permissions and limitations
# under the License.
-require 'deltacloud/helpers/application_helper'
-require 'deltacloud/helpers/json_helper'
-require 'deltacloud/helpers/conversion_helper'
-require 'deltacloud/helpers/hardware_profiles_helper'
-require 'deltacloud/helpers/blob_stream'
+require_relative 'helpers/driver_helper'
+require_relative 'helpers/auth_helper'
+require_relative 'helpers/url_helper'
+require_relative 'helpers/assets_helper'
+require_relative 'helpers/deltacloud_helper'
+require_relative 'helpers/rabbit_helper'
+require_relative 'helpers/blob_stream_helper'
+require_relative 'core_ext/string'
+require_relative 'core_ext/array'
+require_relative 'core_ext/hash'
+require_relative 'core_ext/integer'
+require_relative 'core_ext/proc'
-helpers ApplicationHelper, ConversionHelper, HardwareProfilesHelper, JSONHelper
+module Deltacloud::Collections
+ class Base < Sinatra::Base
+
+ extend Deltacloud::Helpers::Drivers
+ include Sinatra::Rabbit::Features
+
+ helpers Deltacloud::Helpers::Drivers
+ helpers Sinatra::AuthHelper
+ helpers Sinatra::UrlForHelper
+ helpers Sinatra::StaticAssets::Helpers
+ helpers Rack::RespondTo::Helpers
+ helpers Deltacloud::Helpers::Application
+
+ register Rack::RespondTo
+
+ enable :xhtml
+ enable :dump_errors
+ enable :show_errors
+ enable :method_override
+ disable :show_exceptions
+
+ set :root_url, API_ROOT_URL
+ set :version, API_VERSION
+ set :root, File.join(File.dirname(__FILE__), '..', '..')
+ set :views, root + '/views'
+ set :public_folder, root + '/public'
+
+ error do
+ report_error
+ end
+
+ error Deltacloud::ExceptionHandler::ValidationFailure do
+ report_error
+ end
+
+ before do
+ # Respond with 400, If we don't get a http Host header,
+ halt 400, "Unable to find HTTP Host header" if @env['HTTP_HOST'] == nil
+ end
+
+ after do
+ headers 'Server' => 'Apache-Deltacloud/' + settings.version
+ end
+
+ def self.new_route_for(route, &block)
+ get route_for('/' + route.to_s + '/new') do
+ instance_eval(&block) if block_given?
+ respond_to do |format|
+ format.html do
+ haml :"#{route}/new"
+ end
+ end
+ end
+ end
+
+ def self.check_capability(opts={})
+ Sinatra::Rabbit.set :check_capability, opts[:for]
+ end
+
+ def self.check_features(opts={})
+ Sinatra::Rabbit.set :check_features, opts[:for]
+ end
+
+ def self.route_for(url)
+ "#{settings.root_url}#{url}"
+ end
+ end
+end
diff --git a/server/lib/deltacloud/models.rb b/server/lib/deltacloud/models.rb
index af02520..099afda 100644
--- a/server/lib/deltacloud/models.rb
+++ b/server/lib/deltacloud/models.rb
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
@@ -14,20 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-require 'deltacloud/models/base_model'
-require 'deltacloud/models/realm'
-require 'deltacloud/models/image'
-require 'deltacloud/models/instance'
-require 'deltacloud/models/key'
-require 'deltacloud/models/address'
-require 'deltacloud/models/instance_address'
-require 'deltacloud/models/instance_profile'
-require 'deltacloud/models/storage_snapshot'
-require 'deltacloud/models/storage_volume'
-require 'deltacloud/models/bucket'
-require 'deltacloud/models/blob'
-require 'deltacloud/models/load_balancer'
-require 'deltacloud/models/firewall'
-require 'deltacloud/models/firewall_rule'
-require 'deltacloud/models/provider'
-require 'deltacloud/models/metric'
+require_relative 'models/base_model'
+
+# Include all models
+
+Dir[File.join(File::dirname(__FILE__), "models", "*.rb")].each do |model|
+ next if model =~ /base_model\.rb$/
+ require model
+end
diff --git a/server/lib/deltacloud/models/hardware_profile.rb b/server/lib/deltacloud/models/hardware_profile.rb
new file mode 100644
index 0000000..45e77a1
--- /dev/null
+++ b/server/lib/deltacloud/models/hardware_profile.rb
@@ -0,0 +1,194 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud
+ class HardwareProfile
+
+ UNITS = {
+ :memory => "MB",
+ :storage => "GB",
+ :architecture => "label",
+ :cpu => "count"
+ }
+
+ def self.unit(name)
+ UNITS[name]
+ end
+
+ class Property
+ attr_reader :name, :kind, :default
+ # kind == :range
+ attr_reader :first, :last
+ # kind == :enum
+ attr_reader :values
+ # kind == :fixed
+ attr_reader :value
+
+ def initialize(name, values, opts = {})
+ @name = name
+ if values.is_a?(Range)
+ @kind = :range
+ @first = values.first
+ @last = values.last
+ @default = values.first
+ elsif values.is_a?(Array)
+ @kind = :enum
+ @values = values
+ @default = values.first
+ else
+ @kind = :fixed
+ @value = values
+ @default = @value
+ end
+ @default = opts[:default] if opts[:default]
+ end
+
+ def unit
+ HardwareProfile.unit(name)
+ end
+
+ def param
+ :"hwp_#{name}"
+ end
+
+ def fixed?
+ kind == :fixed
+ end
+
+ def valid?(v)
+ v = convert_property_value_type(v)
+ case kind
+ # NOTE:
+ # Currently we cannot validate fixed values because of UI
+ # limitation. In UI we have multiple hwp_* properties which overide
+ # each other.
+ # Then provider have one 'static' hardware profile and one
+ # 'customizable' when user select the static one the UI also send
+ # values from the customizable one (which will lead to a validation
+ # error because validation algorith will think that client want to
+ # overide fixed values.
+ #
+ # when :fixed then (v == @default.to_s)
+ when :fixed then true
+ when :range then match_type?(first, v) and (first..last).include?(v)
+ when :enum then match_type?(values.first, v) and values.include?(v)
+ else false
+ end
+ end
+
+ def to_param
+ if defined? Sinatra::Rabbit
+ Sinatra::Rabbit::Param.new([param, :string, :optional, []])
+ end
+ end
+
+ def include?(v)
+ if kind == :fixed
+ return v == value
+ else
+ return values.include?(v)
+ end
+ end
+
+ private
+
+ def match_type?(reference, value)
+ true if reference.class == value.class
+ end
+
+ def convert_property_value_type(v)
+ return v.to_f if v =~ /(\d+)\.(\d+)/
+ return v.to_i if v =~ /(\d+)/
+ v.to_s
+ end
+ end
+
+ class << self
+ def property(prop)
+ define_method(prop) do |*args|
+ values, opts, *ignored = *args
+ instvar = :"@#{prop}"
+ unless values.nil?
+ @properties[prop] = Property.new(prop, values, opts || {})
+ end
+ @properties[prop]
+ end
+ end
+ end
+
+ attr_reader :name
+ property :cpu
+ property :architecture
+ property :memory
+ property :storage
+
+ def initialize(name,&block)
+ @properties = {}
+ @name = name
+ instance_eval &block if block_given?
+ end
+
+ def each_property(&block)
+ @properties.each_value { |prop| yield prop }
+ end
+
+ def properties
+ @properties.values
+ end
+
+ def property(name)
+ @properties[name.to_sym]
+ end
+
+ def default?(prop, v)
+ p = @properties[prop.to_sym]
+ p && p.default.to_s == v
+ end
+
+ def to_hash
+ props = []
+ self.each_property do |p|
+ if p.kind.eql? :fixed
+ props << { :kind => p.kind, :value => p.value, :name => p.name, :unit => p.unit }
+ else
+ param = { :operation => "create", :method => "post", :name => p.name }
+ if p.kind.eql? :range
+ param[:range] = { :first => p.first, :last => p.last }
+ elsif p.kind.eql? :enum
+ param[:enum] = p.values.collect { |v| { :entry => v } }
+ end
+ param
+ props << { :kind => p.kind, :value => p.default, :name => p.name, :unit => p.unit, :param => param }
+ end
+ end
+ {
+ :id => self.name,
+ :properties => props
+ }
+ end
+
+ def include?(prop, v)
+ p = @properties[prop]
+ p.nil? || p.include?(v)
+ end
+
+ def params
+ @properties.values.inject([]) { |m, prop|
+ m << prop.to_param
+ }.compact
+ end
+ end
+end
diff --git a/server/lib/deltacloud/models/state_machine.rb b/server/lib/deltacloud/models/state_machine.rb
new file mode 100644
index 0000000..19fb9f2
--- /dev/null
+++ b/server/lib/deltacloud/models/state_machine.rb
@@ -0,0 +1,99 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module Deltacloud
+ class StateMachine
+
+ attr_reader :states
+ def initialize(&block)
+ @states = []
+ instance_eval &block if block
+ end
+
+ def start()
+ state(:start)
+ end
+
+ def finish()
+ state(:finish)
+ end
+
+ def state(name)
+ state = @states.find{|e| e.name == name.to_sym}
+ if ( state.nil? )
+ state = State.new( self, name.to_sym )
+ @states << state
+ end
+ state
+ end
+
+ def method_missing(sym,*args)
+ return state( sym ) if ( args.empty? )
+ super( sym, *args )
+ end
+
+ class State
+
+ attr_reader :name
+ attr_reader :transitions
+
+ def initialize(machine, name)
+ @machine = machine
+ @name = name
+ @transitions = []
+ end
+
+ def to_s
+ self.name.to_s
+ end
+
+ def to(destination_name)
+ destination = @machine.state(destination_name)
+ transition = Transition.new( @machine, destination )
+ @transitions << transition
+ transition
+ end
+
+ end
+
+ class Transition
+
+ attr_reader :destination
+ attr_reader :action
+
+ def initialize(machine, destination)
+ @machine = machine
+ @destination = destination
+ @auto = false
+ @action = nil
+ end
+
+ def automatically
+ @auto = true
+ end
+
+ def automatically?
+ @auto
+ end
+
+ def on(action)
+ @action = action
+ end
+
+ end
+
+ end
+end
diff --git a/server/lib/deltacloud/state_machine.rb b/server/lib/deltacloud/state_machine.rb
deleted file mode 100644
index facd4ba..0000000
--- a/server/lib/deltacloud/state_machine.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership. The
-# ASF licenses this file to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance with the
-# License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-module Deltacloud
- class StateMachine
-
- attr_reader :states
- def initialize(opts = {}, &block)
- @all_states = opts[:all_states]
- @all_actions = opts[:all_actions]
- @states = []
- instance_eval &block if block
- end
-
- def start()
- state(:start)
- end
-
- def finish()
- state(:finish)
- end
-
- def state(name)
- unless valid_state_name?(name)
- raise "State '#{name}' not in list of allowed states"
- end
- state = @states.find{|e| e.name == name.to_sym}
- if ( state.nil? )
- state = State.new( self, name.to_sym )
- @states << state
- end
- state
- end
-
- def valid_state_name?(name)
- @all_states.nil? || @all_states.include?(name.to_sym)
- end
-
- def valid_action_name?(name)
- @all_actions.nil? || @all_actions.include?(name.to_sym)
- end
-
- def method_missing(sym,*args)
- return state( sym ) if ( args.empty? )
- super( sym, *args )
- end
-
- class State
-
- attr_reader :name
- attr_reader :transitions
-
- def initialize(machine, name)
- @machine = machine
- @name = name
- @transitions = []
- end
-
- def to_s
- self.name.to_s
- end
-
- def to(destination_name)
- destination = @machine.state(destination_name)
- transition = Transition.new( @machine, destination )
- @transitions << transition
- transition
- end
-
- end
-
- class Transition
-
- attr_reader :destination
- attr_reader :action
-
- def initialize(machine, destination)
- @machine = machine
- @destination = destination
- @auto = false
- @action = nil
- end
-
- def automatically
- @auto = true
- end
-
- def automatically?
- @auto
- end
-
- def on(action)
- unless @machine.valid_action_name?(action)
- raise "Action '#{action}' not in list of allowed actions"
- end
- @action = action
- end
-
- end
-
- end
-end
--
1.7.10.1