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 2013/03/07 14:10:24 UTC
[PATCH core 1/5] Client: Complete rewrite of deltacloud-client
From: Michal Fojtik <mf...@redhat.com>
- Now use Faraday HTTP lib
- Superb error reporting (based on Faraday middleware)
- Documentation
- Easy to read/fix bugs/add features
- Compatible with jRuby/MRI 2.0.0
Signed-off-by: Michal fojtik <mf...@redhat.com>
---
client/.gitignore | 21 +-
client/Gemfile | 12 +
client/README | 127 ------
client/README.md | 73 ++++
client/Rakefile | 92 +++-
client/deltacloud-client.gemspec | 13 +-
client/lib/base_object.rb | 386 ----------------
client/lib/client_bucket_methods.rb | 69 ---
client/lib/deltacloud.rb | 486 ---------------------
client/lib/deltacloud/client.rb | 78 ++++
client/lib/deltacloud/client/base_error.rb | 84 ++++
client/lib/deltacloud/client/connection.rb | 135 ++++++
.../lib/deltacloud/client/helpers/model_helper.rb | 69 +++
.../deltacloud/client/helpers/property_helper.rb | 103 +++++
client/lib/deltacloud/client/helpers/xml_helper.rb | 33 ++
client/lib/deltacloud/client/methods.rb | 29 ++
client/lib/deltacloud/client/methods/address.rb | 68 +++
client/lib/deltacloud/client/methods/api.rb | 96 ++++
.../client/methods/backward_compatiblity.rb | 72 +++
client/lib/deltacloud/client/methods/blob.rb | 91 ++++
client/lib/deltacloud/client/methods/bucket.rb | 56 +++
client/lib/deltacloud/client/methods/common.rb | 46 ++
client/lib/deltacloud/client/methods/driver.rb | 54 +++
client/lib/deltacloud/client/methods/firewall.rb | 66 +++
.../deltacloud/client/methods/hardware_profile.rb | 42 ++
client/lib/deltacloud/client/methods/image.rb | 62 +++
client/lib/deltacloud/client/methods/instance.rb | 140 ++++++
.../deltacloud/client/methods/instance_state.rb | 41 ++
client/lib/deltacloud/client/methods/key.rb | 59 +++
client/lib/deltacloud/client/methods/realm.rb | 43 ++
.../deltacloud/client/methods/storage_snapshot.rb | 62 +++
.../deltacloud/client/methods/storage_volume.rb | 95 ++++
client/lib/deltacloud/client/models.rb | 30 ++
client/lib/deltacloud/client/models/address.rb | 57 +++
client/lib/deltacloud/client/models/base.rb | 151 +++++++
client/lib/deltacloud/client/models/blob.rb | 56 +++
client/lib/deltacloud/client/models/bucket.rb | 65 +++
client/lib/deltacloud/client/models/driver.rb | 87 ++++
client/lib/deltacloud/client/models/firewall.rb | 70 +++
.../deltacloud/client/models/hardware_profile.rb | 68 +++
client/lib/deltacloud/client/models/image.rb | 60 +++
client/lib/deltacloud/client/models/instance.rb | 122 ++++++
.../deltacloud/client/models/instance_address.rb | 35 ++
.../lib/deltacloud/client/models/instance_state.rb | 52 +++
client/lib/deltacloud/client/models/key.rb | 52 +++
client/lib/deltacloud/client/models/realm.rb | 29 ++
.../deltacloud/client/models/storage_snapshot.rb | 54 +++
.../lib/deltacloud/client/models/storage_volume.rb | 96 ++++
client/lib/deltacloud/core_ext.rb | 19 +
client/lib/deltacloud/core_ext/element.rb | 32 ++
client/lib/deltacloud/core_ext/fixnum.rb | 30 ++
client/lib/deltacloud/core_ext/nil.rb | 22 +
client/lib/deltacloud/core_ext/string.rb | 49 +++
client/lib/deltacloud/error_response.rb | 92 ++++
client/lib/documentation.rb | 59 ---
client/lib/errors.rb | 140 ------
client/lib/hwp_properties.rb | 61 ---
client/lib/instance_state.rb | 44 --
client/lib/string.rb | 59 ---
client/support/method_test_template.erb | 53 +++
client/support/methods_template.erb | 54 +++
client/support/model_template.erb | 45 ++
62 files changed, 3304 insertions(+), 1442 deletions(-)
create mode 100644 client/Gemfile
delete mode 100644 client/README
create mode 100644 client/README.md
delete mode 100644 client/lib/base_object.rb
delete mode 100644 client/lib/client_bucket_methods.rb
delete mode 100644 client/lib/deltacloud.rb
create mode 100644 client/lib/deltacloud/client.rb
create mode 100644 client/lib/deltacloud/client/base_error.rb
create mode 100644 client/lib/deltacloud/client/connection.rb
create mode 100644 client/lib/deltacloud/client/helpers/model_helper.rb
create mode 100644 client/lib/deltacloud/client/helpers/property_helper.rb
create mode 100644 client/lib/deltacloud/client/helpers/xml_helper.rb
create mode 100644 client/lib/deltacloud/client/methods.rb
create mode 100644 client/lib/deltacloud/client/methods/address.rb
create mode 100644 client/lib/deltacloud/client/methods/api.rb
create mode 100644 client/lib/deltacloud/client/methods/backward_compatiblity.rb
create mode 100644 client/lib/deltacloud/client/methods/blob.rb
create mode 100644 client/lib/deltacloud/client/methods/bucket.rb
create mode 100644 client/lib/deltacloud/client/methods/common.rb
create mode 100644 client/lib/deltacloud/client/methods/driver.rb
create mode 100644 client/lib/deltacloud/client/methods/firewall.rb
create mode 100644 client/lib/deltacloud/client/methods/hardware_profile.rb
create mode 100644 client/lib/deltacloud/client/methods/image.rb
create mode 100644 client/lib/deltacloud/client/methods/instance.rb
create mode 100644 client/lib/deltacloud/client/methods/instance_state.rb
create mode 100644 client/lib/deltacloud/client/methods/key.rb
create mode 100644 client/lib/deltacloud/client/methods/realm.rb
create mode 100644 client/lib/deltacloud/client/methods/storage_snapshot.rb
create mode 100644 client/lib/deltacloud/client/methods/storage_volume.rb
create mode 100644 client/lib/deltacloud/client/models.rb
create mode 100644 client/lib/deltacloud/client/models/address.rb
create mode 100644 client/lib/deltacloud/client/models/base.rb
create mode 100644 client/lib/deltacloud/client/models/blob.rb
create mode 100644 client/lib/deltacloud/client/models/bucket.rb
create mode 100644 client/lib/deltacloud/client/models/driver.rb
create mode 100644 client/lib/deltacloud/client/models/firewall.rb
create mode 100644 client/lib/deltacloud/client/models/hardware_profile.rb
create mode 100644 client/lib/deltacloud/client/models/image.rb
create mode 100644 client/lib/deltacloud/client/models/instance.rb
create mode 100644 client/lib/deltacloud/client/models/instance_address.rb
create mode 100644 client/lib/deltacloud/client/models/instance_state.rb
create mode 100644 client/lib/deltacloud/client/models/key.rb
create mode 100644 client/lib/deltacloud/client/models/realm.rb
create mode 100644 client/lib/deltacloud/client/models/storage_snapshot.rb
create mode 100644 client/lib/deltacloud/client/models/storage_volume.rb
create mode 100644 client/lib/deltacloud/core_ext.rb
create mode 100644 client/lib/deltacloud/core_ext/element.rb
create mode 100644 client/lib/deltacloud/core_ext/fixnum.rb
create mode 100644 client/lib/deltacloud/core_ext/nil.rb
create mode 100644 client/lib/deltacloud/core_ext/string.rb
create mode 100644 client/lib/deltacloud/error_response.rb
delete mode 100644 client/lib/documentation.rb
delete mode 100644 client/lib/errors.rb
delete mode 100644 client/lib/hwp_properties.rb
delete mode 100644 client/lib/instance_state.rb
delete mode 100644 client/lib/string.rb
create mode 100644 client/support/method_test_template.erb
create mode 100644 client/support/methods_template.erb
create mode 100644 client/support/model_template.erb
diff --git a/client/.gitignore b/client/.gitignore
index b4b07a3..3a0a93e 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -1,4 +1,19 @@
-spec_report.html
-*.swp
-tmp/
*.gem
+*.rbc
+.bundle
+.config
+coverage
+InstalledFiles
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
+*.lock
+
+# YARD artifacts
+.yardoc
+_yardoc
+doc/
diff --git a/client/Gemfile b/client/Gemfile
new file mode 100644
index 0000000..4c6d274
--- /dev/null
+++ b/client/Gemfile
@@ -0,0 +1,12 @@
+source 'https://rubygems.org'
+
+gem 'faraday'
+gem 'nokogiri'
+
+group :development do
+ gem 'rake'
+ gem 'minitest'
+ gem 'vcr'
+ gem 'pry'
+ gem 'simplecov', :require => false
+end
diff --git a/client/README b/client/README
deleted file mode 100644
index 5324062..0000000
--- a/client/README
+++ /dev/null
@@ -1,127 +0,0 @@
-# Deltacloud Client (Ruby)
-
-The Deltacloud project includes a Ruby client. Other language-bindings
-are possible and will be supported soon. The client aims to insulate
-users from having to deal with HTTP and REST directly.
-
-Each resource type has an associated model to ease usage. Where
-resource reference other resources, natural navigation across the
-object model is possible.
-
-For example
-
- puts instance.image.name
- puts instance.hardware_profile.architecture
-
-## Basics
-
-To use the client, you must require `deltacloud`.
-
- require 'deltacloud'
-
-## Connecting to a Deltacloud provider
-
- require 'deltacloud'
-
- api_url = 'http://localhost:3001/api'
- api_name = 'mockuser'
- api_password = 'mockpassword'
-
- client = DeltaCloud.new( api_name, api_password, api_url )
-
- # work with client here
-
-In addition to creating a client, operations may occur within a block
-included on the initialization
-
- DeltaCloud.new( api_name, api_password, api_url ) do |client|
- # work with client here
- end
-
-In the event of a failure, any underlying HTTP transport exceptions
-will be thrown all the way out to the caller.
-
-## Listing realms
-
-You may retrieve a complete list of realms available to you
-
- realms = client.realms
-
-You may retrieve a specific realm by its identifier
-
- realm = client.realm( 'us' )
-
-## Listing hardware profiles
-
-You may retrieve a complete list of hardware profiles available for launching
-machines
-
- hwp = client.hardware_profiles
-
-You may filter hardware profiles by architecture
-
- flavors = client.hardware_profiles( :architecture=>'x86_64' )
-
-You may retrieve a specific hardware profile by its identifier
-
- flavor = client.hardware_profile( 'm1-small' )
-
-## Listing images
-
-You may retrieve a complete list of images
-
- images = client.images
-
-You may retrieve a list of images owned by the currently authenticated
-user
-
- images = client.images( :owner_id=>:self )
-
-You may retrieve a list of images visible to you but owned by a specific
-user
-
- images = client.images( :owner_id=>'daryll' )
-
-You may retrieve a specific image by its identifier
-
- image = client.image( 'ami-8675309' )
-
-## Listing instances
-
-You may retrieve a list of all instances visible to you
-
- instances = client.instances
-
-You may retrieve a specific instance by its identifier
-
- instance = client.instance( 'i-90125' )
-
-## Launching instances
-
-An instance may be launched using just an image identifier
-
- image = client.image( 'ami-8675309' )
- instance = client.create_instance( image.id )
-
-Optionally, a flavor or realm may be specified
-
- instance = client.create_instance( image.id, :flavor=>'m1-small', :realm=>'us' )
-
-## Manipulating instances
-
-Given an instance, depending on its state, various actions _may_ be available.
-
-To determine what's available, the `instance#actions` method may be used.
-
- instance.actions # [ 'reboot', 'stop' ]
-
-For a valid action, the method matching the action with an exclamation point may be called.
-
- instance.reboot!
-
-Upon invoking an action, the instance will refresh its contents, in case the state has changed.
-To determine later if the state has changed again, the instance must be refetched using
-the `client.instance(...)` method.
-
-
-
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..fcd7595
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,73 @@
+# deltacloud-client
+
+The Deltacloud project includes a Ruby client. Other language-bindings
+are possible and will be supported soon. The client aims to insulate
+users from having to deal with HTTP and REST directly.
+
+Each resource type has an associated model to ease usage. Where
+resource reference other resources, natural navigation across the
+object model is possible.
+
+This is a Ruby client library for the [Deltacloud API](http://deltacloud.apache.org).
+
+## Usage
+
+```ruby
+require 'deltacloud/client'
+
+API_URL = "http://localhost:3001/api" # Deltacloud API endpoint
+
+# Simple use-cases
+client = Deltacloud::Client(API_URL, 'mockuser', 'mockpassword')
+
+pp client.instances # List all instances
+pp client.instance('i-12345') # Get one instance
+
+inst = client.create_instance 'ami-1234', :hwp_id => 'm1.small' # Create instance
+
+inst.reboot! # Reboot instance
+
+# Advanced usage
+
+# Deltacloud API supports changing driver per-request:
+
+client.use(:ec2, 'API_KEY', 'API_SECRET').instances # List EC2 instances
+client.use(:openstack, 'admin@tenant', 'password', KEYSTONE_URL).instances # List Openstack instances
+
+```
+# Want help?
+
+## Adding new Deltacloud collection to client
+
+```
+$ rake generate[YOUR_COLLECTION] # eg. 'storage_snapshot'
+# Hit Enter 2x
+```
+
+- Edit `lib/deltacloud/client/methods/YOUR_COLLECTION.rb` and add all
+ methods for manipulating your collection. The list/show methods
+ should already be generated for you, but double-check them.
+
+- Edit `lib/deltacloud/client/model/YOUR_COLLECTION.rb` and add model
+ methods. Model methods should really be just a syntax sugar and exercise
+ the *Deltacloud::Client::Methods* methods.
+ The purpose of *model* class life is to deserialize XML body received
+ from Deltacloud API to a Ruby class.
+
+## Debugging a nasty bug?
+
+- You can easily debug deltacloud-client using powerful **pry**.
+
+ - `gem install deltacloud-core`
+ - optional: `rbenv rehash` ;-)
+ - `deltacloudd -i mock -p 3002`
+ - `rake console`
+
+Console require **pry** gem installed. If you are not using this awesome
+gem, you can fix it by `gem install pry`.
+
+# License
+
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
diff --git a/client/Rakefile b/client/Rakefile
index 4248ca5..17d0cdd 100644
--- a/client/Rakefile
+++ b/client/Rakefile
@@ -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,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+require 'rubygems'
require 'rubygems/package_task'
+require 'rake'
require 'rake/testtask'
-load 'deltacloud-client.gemspec'
+require 'pry' rescue LoadError
spec = Gem::Specification.load('deltacloud-client.gemspec')
@@ -25,15 +26,98 @@ Gem::PackageTask.new(spec) do |pkg|
pkg.need_tar = true
end
-desc "Re-install the deltacloud-client gem"
+desc "Re-install the deltacloud-client gem (used for development)"
task :reinstall do
puts %x{gem uninstall deltacloud-client --all -I -x}
puts %x{gem build deltacloud-client.gemspec}
puts %x{gem install deltacloud-client-*.gem --local}
end
+desc 'Generate model/methods files for collection.'
+task :generate, :name do |t, args|
+ require 'erb'
+ require_relative './lib/deltacloud/core_ext'
+ model_tpl = ERB.new(File.read('support/model_template.erb'))
+ methods_tpl = ERB.new(File.read('support/methods_template.erb'))
+ name = args[:name]
+ model_file = "lib/deltacloud/client/models/#{name}.rb"
+ methods_file = "lib/deltacloud/client/methods/#{name}.rb"
+ puts model_body = model_tpl.result(binding)
+ print "Save model to '#{model_file}'? [Y/n]"
+ answer = $stdin.gets.chomp
+ if answer.empty? or answer == 'Y'
+ File.open(model_file, 'w') { |f|
+ f.write(model_body)
+ }
+ File.open('lib/deltacloud/client/models.rb', 'a') { |f|
+ f.puts "require_relative './models/#{name}'"
+ }
+ end
+ puts methods_body = methods_tpl.result(binding)
+ print "Save methods to '#{methods_file}'? [Y/n]"
+ answer = $stdin.gets.chomp
+ if answer.empty? or answer == 'Y'
+ File.open(methods_file, 'w') { |f|
+ f.write(methods_body)
+ }
+ File.open('lib/deltacloud/client/methods.rb', 'a') { |f|
+ f.puts "require_relative './methods/#{name}'"
+ }
+ end
+ puts
+ puts "Don't forget to add this line to 'lib/deltacloud/client/connection.rb':"
+ puts
+ puts "include Deltacloud::Client::Methods::#{name.to_s.camelize}"
+ puts
+end
+
+desc 'Generate method test file'
+task :test_generate, :name do |t, args|
+ require 'erb'
+ require_relative './lib/deltacloud/core_ext'
+ method_tpl = ERB.new(File.read('support/method_test_template.erb'))
+ name = args[:name]
+ methods_file = "tests/methods/#{name}_test.rb"
+ puts method_body = method_tpl.result(binding)
+ print "Save method test to '#{methods_file}'? [Y/n]"
+ answer = $stdin.gets.chomp
+ if answer.empty? or answer == 'Y'
+ File.open(methods_file, 'w') { |f|
+ f.write(method_body)
+ }
+ end
+end
+
+
+desc "Open console with client connected to #{ENV['API_URL'] || 'localhost:3002/api'}"
+task :console do
+ unless binding.respond_to? :pry
+ puts 'To open a console, you need to have "pry" installed (gem install pry)'
+ exit(1)
+ end
+ require_relative './lib/deltacloud/client'
+ client = Deltacloud::Client(
+ ENV['API_URL'] || 'http://localhost:3002/api',
+ ENV['API_USER'] || 'mockuser',
+ ENV['API_PASSWORD'] || 'mockpassword'
+ )
+ binding.pry
+end
+
Rake::TestTask.new(:test) do |t|
t.test_files = FileList[
- 'tests/*test.rb', # EC2 frontend internal API tests
+ 'tests/*/*_test.rb'
]
end
+
+desc "Execute test against live Deltacloud API"
+task :test_live do
+ ENV['NO_VCR'] = 'true'
+ Rake::Task[:test].invoke
+end
+
+desc "Generate test coverage report"
+task :coverage do
+ ENV['COVERAGE'] = 'true'
+ Rake::Task[:test].invoke
+end
diff --git a/client/deltacloud-client.gemspec b/client/deltacloud-client.gemspec
index 1b14f43..874e2f0 100644
--- a/client/deltacloud-client.gemspec
+++ b/client/deltacloud-client.gemspec
@@ -20,14 +20,19 @@ Gem::Specification.new do |s|
s.homepage = "http://www.deltacloud.org"
s.email = 'dev@deltacloud.apache.org'
s.name = 'deltacloud-client'
- s.description = %q{Deltacloud REST Client for API}
+ s.description = %q{A REST client for the Deltacloud API}
s.version = Deltacloud::API_VERSION
s.summary = %q{Deltacloud REST Client}
s.files = Dir['Rakefile', 'lib/**/*.rb']
s.test_files= Dir.glob("tests/**/**")
- s.extra_rdoc_files = Dir["LICENSE", "NOTICE", "DISCLAIMER"]
+ s.extra_rdoc_files = Dir["LICENSE", "NOTICE", "README.md"]
- s.add_dependency('rest-client', '>= 1.6.1')
+ s.add_dependency('faraday', '>=0.8.6')
s.add_dependency('nokogiri', '>= 1.4.3')
- s.add_development_dependency('rspec', '>= 2.0.0')
+
+ s.add_development_dependency('minitest')
+ s.add_development_dependency('simplecov')
+ s.add_development_dependency('vcr')
+ s.add_development_dependency('rake')
+ s.add_development_dependency('pry')
end
diff --git a/client/lib/base_object.rb b/client/lib/base_object.rb
deleted file mode 100644
index 6a28d5a..0000000
--- a/client/lib/base_object.rb
+++ /dev/null
@@ -1,386 +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_relative './string.rb'
-
-module DeltaCloud
-
- class BaseObjectParamError < Exception; end
- class NoHandlerForMethod < Exception; end
-
- # BaseObject model basically provide the basic operation around
- # REST model, like defining a links between different objects,
- # element with text values, or collection of these elements
- class BaseObject
- attr_reader :id, :url, :client, :base_name
- attr_reader :objects
-
- alias :uri :url
-
- # For initializing new object you require to set
- # id, url, client and name attribute.
- def initialize(opts={}, &block)
- @id, @url, @client, @base_name = opts[:id], opts[:url], opts[:client], opts[:name]
- @objects = []
- raise BaseObjectParamError if @id.nil? or @url.nil? or @client.nil? or @base_name.nil?
- yield self if block_given?
- end
-
- # This method add link to another object in REST model
- # XML syntax: <link rel="destroy" href="http://localhost/api/resource" method="post"/>
- def add_link!(object_name, id)
- @objects << {
- :type => :link,
- :method_name => object_name.sanitize,
- :id => id
- }
- @objects << {
- :type => :text,
- :method_name => "#{object_name.sanitize}_id",
- :value => id
- }
- end
-
- # Method add property for hardware profile
- def add_hwp_property!(name, property, type)
- hwp_property=case type
- when :float then DeltaCloud::HWP::FloatProperty.new(property, name)
- when :integer then DeltaCloud::HWP::Property.new(property, name)
- end
- @objects << {
- :type => :property,
- :method_name => name.sanitize,
- :property => hwp_property
- }
- end
-
- # This method define text object in REST model
- # XML syntax: <name>Instance 1</name>
- def add_text!(object_name, value)
- @objects << {
- :type => :text,
- :method_name => object_name.sanitize,
- :value => value
- }
- end
-
- def add_authentication!(auth_type, values=[])
- value = { :key => (values/'login/keyname').text.strip } if auth_type == 'key'
- if auth_type == 'password'
- value = {
- :username => (values/'login/username').text.strip,
- :username => (values/'login/password').text.strip
- }
- end
- @objects << {
- :type => :collection,
- :method_name => 'authentication',
- :values => value
- }
- end
-
- def add_provider!(provider_id, entrypoints)
- @providers ||= []
- @providers << {
- provider_id.intern => entrypoints.map { |e| { :kind => e[:kind], :url => e.text } }
- }
- @objects << {
- :type => :collection,
- :method_name => 'providers',
- :values => @providers
- }
- end
-
-
- # This method define collection of text elements inside REST model
- # XML syntax: <addresses>
- # <address>127.0.0.1</address>
- # <address>127.0.0.2</address>
- # </addresses>
- def add_addresses!(collection_name, values=[])
- @objects << {
- :type => :collection,
- :method_name => collection_name.sanitize,
- :values => values.collect { |v| { :address => v.text.strip, :type => v[:type] }}
- }
- end
-
- # This method define collection of text elements inside REST model
- # XML syntax: <addresses>
- # <address>127.0.0.1</address>
- # <address>127.0.0.2</address>
- # </addresses>
- def add_collection!(collection_name, values=[])
- @objects << {
- :type => :collection,
- :method_name => collection_name.sanitize,
- :values => values
- }
- end
-
- # Basic method hander. This define a way how value from property
- # will be returned
- def method_handler(m, args=[])
- case m[:type]
- when :link then return @client.send(m[:method_name].singularize, m[:id])
- when :text then return m[:value]
- when :property then return m[:property]
- when :collection then return m[:values]
- when :list then return m[:value].join(", ")
- else raise NoHandlerForMethod
- end
- end
-
- def method_missing(method_name, *args)
- # First of all search throught array for method name
- m = search_for_method(method_name)
- if m.nil?
- if method_name == :"valid_provider?"
- return providers.any? { |p| p.keys.include? args.first.to_sym }
- end
- if method_name == :"valid_provider_url?"
- return providers.any? { |p| !p.find { |k, v| v.find { |u| u[:url] == args.first } }.nil? }
- end
- super
- else
- # Call appropriate handler for method
- method_handler(m, args)
- end
- end
-
- # This method adds blobs to the blob_list property
- # of a bucket
- def add_blob!(blob_name)
- if @blob_list.nil?
- @blob_list = [blob_name]
- @objects << {
- :type => :list,
- :method_name => "blob_list",
- :value => @blob_list
- }
- else
- @blob_list << blob_name
- current = search_for_method('blob_list')
- current[:value] = @blob_list
- end
- end
-
- private
-
- def search_for_method(name)
- @objects.detect { |o| o[:method_name] == "#{name}" }
- end
-
- end
-
- class ActionObject < BaseObject
-
- def initialize(opts={}, &block)
- super(opts)
- @action_urls = opts[:action_urls] || []
- @actions = []
- end
-
- # This trigger is called right after action.
- # This method does nothing inside ActionObject
- # but it can be redifined and used in meta-programming
- def action_trigger(action)
- end
-
- def add_action_link!(id, link)
- m = {
- :type => :action_link,
- :method_name => "#{link['rel'].sanitize}!",
- :id => id,
- :href => link['href'],
- :rel => link['rel'].sanitize,
- :method => link['method'].sanitize
- }
- @objects << m
- @actions << [m[:rel], m[:href]]
- @action_urls << m[:href]
- end
-
- def actions
- @objects.inject([]) do |result, item|
- result << [item[:rel], item[:href]] if item[:type].eql?(:action_link)
- result
- end
- end
-
- def action_urls
- actions.collect { |a| a.last }
- end
-
- alias :base_method_handler :method_handler
-
- # First call BaseObject method handler,
- # then, if not method found try ActionObject handler
- def method_handler(m, args=[])
- begin
- base_method_handler(m, args)
- rescue NoHandlerForMethod
- case m[:type]
- when :action_link then do_action(m, args)
- else raise NoHandlerForMethod
- end
- end
- end
-
- alias :original_method_missing :method_missing
-
- def method_missing(name, *args)
- if name.to_s =~ /^has_(\w+)\?$/
- return actions.any? { |a| a[0] == $1 }
- end
- original_method_missing(name, args)
- end
-
- private
-
- def do_action(m, args)
- args = args.first || {}
- method = m[:method].to_sym
- @client.request(method,
- m[:href],
- method == :get ? args : {},
- method == :get ? {} : args)
- action_trigger(m[:rel])
- end
-
- end
-
- class StatefulObject < ActionObject
- attr_reader :state
-
- def initialize(opts={}, &block)
- super(opts)
- @state = opts[:initial_state] || ''
- add_default_states!
- end
-
- def add_default_states!
- @objects << {
- :method_name => 'stopped?',
- :type => :state,
- :state => 'STOPPED'
- }
- @objects << {
- :method_name => 'running?',
- :type => :state,
- :state => 'RUNNING'
- }
- @objects << {
- :method_name => 'pending?',
- :type => :state,
- :state => 'PENDING'
- }
- @objects << {
- :method_name => 'shutting_down?',
- :type => :state,
- :state => 'SHUTTING_DOWN'
- }
- end
-
- def action_trigger(action)
- # Refresh object state after action unless the object was destroyed
- return if action.to_s == "destroy"
- @new_state_object = @client.send(self.base_name, self.id)
- @state = @new_state_object.state
- self.update_actions!
- end
-
- def add_run_action!(id, link)
- @objects << {
- :method_name => 'run',
- :type => :run,
- :url => link,
- }
- end
-
- alias :action_method_handler :method_handler
-
- def method_handler(m, args=[])
- begin
- action_method_handler(m, args)
- rescue NoHandlerForMethod
- case m[:type]
- when :state then evaluate_state(m[:state], @state)
- when :run then run_command(m[:url][:href], args)
- else raise NoHandlerForMethod
- end
- end
- end
-
-# private
-
- def run_command(instance_url, args)
- credentials = args[1]
- params = {
- :cmd => args[0],
- :private_key => credentials[:pem] ? File.read(credentials[:pem]) : nil,
- }
- params.merge!({
- :username => credentials[:username],
- :password => credentials[:password]
- }) if credentials[:username] and credentials[:password]
- @client.request(:post, instance_url, {}, params) do |response|
- output = Nokogiri::XML(response)
- (output/'/instance/output').first.text
- end
- end
-
- def evaluate_state(method_state, current_state)
- method_state.eql?(current_state)
- end
-
- def action_objects
- @objects.select { |o| o[:type] == :action_link }
- end
-
- def update_actions!
- new_actions = @new_state_object.action_objects
- @objects.reject! { |o| o[:type] == :action_link }
- @objects = (@objects + new_actions)
- end
-
- end
-
- def self.add_class(name, parent=:base)
- parent = parent.to_s
- parent_class = "#{parent.classify}Object"
- @defined_classes ||= []
- class_name = "#{parent.classify}::#{name.classify}"
- unless @defined_classes.include?(class_name)
- DeltaCloud::API.class_eval("class #{class_name} < DeltaCloud::#{parent_class}; end")
- @defined_classes << class_name
- end
-
- DeltaCloud::API.const_get(parent.classify).const_get(name.classify)
- end
-
- def self.guess_model_type(response)
- response = Nokogiri::XML(response.to_s)
- return :action if ((response/'//actions').length >= 1) and ((response/'//state').length == 0)
- return :stateful if ((response/'//actions').length >= 1) and ((response/'//state').length >= 1)
- return :base
- end
-
- class API
- class Action; end
- class Base; end
- class Stateful; end
- end
-end
diff --git a/client/lib/client_bucket_methods.rb b/client/lib/client_bucket_methods.rb
deleted file mode 100644
index a3dfda0..0000000
--- a/client/lib/client_bucket_methods.rb
+++ /dev/null
@@ -1,69 +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 ClientBucketMethods
-
- def create_bucket(params)
- obj = nil
- request(:post, "#{api_uri.to_s}/buckets", {:name => params['id'],:location=>params['bucket_location'] }) do |response|
- handle_backend_error(response) if response.code!=201
- obj = base_object(:bucket, response)
- end
- end
-
- def destroy_bucket(params)
- #actually response here is 204 - no content - so nothing returned to client?
- request(:delete, "#{api_uri.to_s}/buckets/#{params['id']}") do |response|
- handle_backend_error(response) if response.code!=204
- nil if response.code == 204
- end
- end
-
- def create_blob(params)
- blob = nil
- resource = RestClient::Resource.new("#{api_uri.to_s}/buckets/#{params['bucket']}", :open_timeout => 10, :timeout => 45)
- headers = default_headers.merge(extended_headers)
- unless params['metadata'].nil?
- metadata_headers = {}
- params['metadata'].each do |k,v|
- metadata_headers["X-Deltacloud-Blobmeta-#{k}"] = v
- end
- headers = headers.merge(metadata_headers)
- end
- resource.send(:post, {:blob_data => File.new(params['file_path'], 'rb'), :blob_id => params[:id]}, headers) do |response, request, block|
- handle_backend_error(response) if response.code.eql?(500)
- blob = base_object(:blob, response)
- yield blob if block_given?
- end
- return blob
- end
-
- def destroy_blob(params)
- request(:delete, "#{api_uri.to_s}/buckets/#{params['bucket']}/#{params[:id]}") do |response|
- handle_backend_error(response) if response.code!=204
- nil if response.code == 204
- end
- end
-
- #RestClient doesn't do streaming 'get' yet - we already opened a pull request on this see
- #https://github.com/archiloque/rest-client/issues/closed#issue/62 - apparently its going to
- #be in the next version - unknown when. For now get full response. FIXME
- def blob_data(params)
- request(:get, "#{api_uri.to_s}/buckets/#{params['bucket']}/#{params[:id]}/content") do |response|
- response
- end
- end
-
-end
diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
deleted file mode 100644
index a58c280..0000000
--- a/client/lib/deltacloud.rb
+++ /dev/null
@@ -1,486 +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 'nokogiri'
-require 'rest_client'
-require 'base64'
-require 'logger'
-require 'require_relative' if RUBY_VERSION =~ /^1\.8/
-require_relative './hwp_properties.rb'
-require_relative './instance_state.rb'
-require_relative './documentation.rb'
-require_relative './base_object.rb'
-require_relative './errors.rb'
-require_relative './client_bucket_methods.rb'
-
-module DeltaCloud
-
- # Get a new API client instance
- #
- # @param [String, user_name] API user name
- # @param [String, password] API password
- # @param [String, url] API URL (eg. http://localhost:3001/api)
- # @return [DeltaCloud::API]
- #def self.new(user_name, password, api_url, opts={}, &block)
- # opts ||= {}
- # API.new(user_name, password, api_url, opts, &block)
- #end
-
- def self.new(user_name, password, api_url, &block)
- API.new(user_name, password, api_url, &block)
- end
-
- # Check given credentials if their are valid against
- # backend cloud provider
- #
- # @param [String, user_name] API user name
- # @param [String, password] API password
- # @param [String, user_name] API URL (eg. http://localhost:3001/api)
- # @return [true|false]
- def self.valid_credentials?(user_name, password, api_url, opts={})
- api=API.new(user_name, password, api_url, opts)
- result = false
- api.request(:get, '', :force_auth => '1') do |response|
- result = true if response.code.eql?(200)
- end
- return result
- end
-
- # Return a API driver for specified URL
- #
- # @param [String, url] API URL (eg. http://localhost:3001/api)
- def self.driver_name(url)
- API.new(nil, nil, url).driver_name
- end
-
- class API
- attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
- attr_reader :api_driver, :api_provider
-
- def initialize(user_name, password, api_url, opts={}, &block)
- opts[:version] = true
- @api_driver, @api_provider = opts[:driver], opts[:provider]
- @username, @password = opts[:username] || user_name, opts[:password] || password
- @api_uri = URI.parse(api_url)
- @features, @entry_points = {}, {}
- @verbose = opts[:verbose] || false
- discover_entry_points
- if entry_points.include?(:buckets)
- extend(ClientBucketMethods)
- end
- yield self if block_given?
- end
-
- # This method can be used to switch back-end cloud
- # for API instance using HTTP headers.
- # Options must include:
- # {
- # :driver => 'rhevm|ec2|gogrid|...',
- # :username => 'API key for backend',
- # :password => 'API secret key for backend',
- # }
- # Optionally you can pass also :provider option to change
- # provider entry-point
- #
- # Example usage:
- # client = Deltacloud::new('url', 'username', 'password')
- # ...
- # client.with_config(:driver => 'ec2', :username => '', :password => '') do |ec2|
- # ec2.realms
- # end
- #
- # Note: After this block finish client instance will be set back to default
- # state
- #
- # @param [Hash, opts] New provider configuration
- def with_config(opts, &block)
- api_instance = self.dup
- api_instance.use_driver(opts[:driver],
- :username => opts[:username],
- :password => opts[:password],
- :provider => opts[:provider])
- yield api_instance if block_given?
- api_instance
- end
-
- def connect(&block)
- yield self
- end
-
- # Return API hostname
- def api_host; @api_uri.host ; end
-
- # Return API port
- def api_port; @api_uri.port ; end
-
- # Return API path
- def api_path; @api_uri.path ; end
-
- # Define methods based on 'rel' attribute in entry point
- # Two methods are declared: 'images' and 'image'
- def declare_entry_points_methods(entry_points)
- API.instance_eval do
- entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
-
- define_method model do |*args|
- request(:get, entry_points[model], args.first) do |response|
- base_object_collection(model, response)
- end
- end
-
- define_method :"#{model.to_s.singularize}" do |*args|
- request(:get, "#{entry_points[model]}/#{args[0]}") do |response|
- base_object(model, response)
- end
- end
-
- define_method :"fetch_#{model.to_s.singularize}" do |url|
- url =~ /\/#{model}\/(.*)$/
- self.send(model.to_s.singularize.to_sym, $1)
- end
-
- end
-
- #define methods for blobs:
- if(entry_points.include?(:buckets))
- define_method :"blob" do |*args|
- bucket = args[0]["bucket"]
- blob = args[0][:id]
- request(:get, "#{entry_points[:buckets]}/#{bucket}/#{blob}") do |response|
- base_object("blob", response)
- end
- end
- end
-
- end
- end
-
- def base_object_collection(model, response)
- Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").collect do |item|
- base_object(model, item.to_s)
- end
- end
-
- # Add default attributes [id and href] to class
- def base_object(model, response)
- c = DeltaCloud.add_class("#{model}", DeltaCloud::guess_model_type(response))
- xml_to_class(c, Nokogiri::XML(response).xpath("#{model.to_s.singularize}").first)
- end
-
- # Convert XML response to defined Ruby Class
- def xml_to_class(base_object, item)
-
- return nil unless item
-
- params = {
- :id => item['id'],
- :url => item['href'],
- :name => item.name,
- :client => self
- }
- params.merge!({ :initial_state => (item/'state').text.sanitize }) if (item/'state').length > 0
-
- obj = base_object.new(params)
- # Traverse across XML document and deal with elements
- item.xpath('./*').each do |attribute|
- # Do a link for elements which are links to other REST models
- if self.entry_points.keys.include?(:"#{attribute.name}s")
- obj.add_link!(attribute.name, attribute['id']) && next unless (attribute.name == 'bucket' && item.name == 'blob')
- end
-
- # Do a HWP property for hardware profile properties
- if attribute.name == 'property'
- if attribute['value'] =~ /^(\d+)\.(\d+)$/
- obj.add_hwp_property!(attribute['name'], attribute, :float) && next
- else
- obj.add_hwp_property!(attribute['name'], attribute, :integer) && next
- end
- end
-
- # If there are actions, add they to ActionObject/StateFullObject
- if attribute.name == 'actions'
- (attribute/'link').each do |link|
- (obj.add_run_action!(item['id'], link) && next) if link[:rel] == 'run'
- obj.add_action_link!(item['id'], link)
- end && next
- end
-
- if attribute.name == 'mount'
- obj.add_link!("instance", (attribute/"./instance/@id").first.value)
- obj.add_text!("device", (attribute/"./device/@name").first.value)
- next
- end
-
- #deal with blob metadata
- if (attribute.name == 'user_metadata')
- meta = {}
- attribute.children.select {|x| x.name=="entry" }.each do |element|
- value = element.content.gsub!(/(\n) +/,'')
- meta[element['key']] = value
- end
- obj.add_collection!(attribute.name, meta.inspect) && next
- end
-
- if (['public_addresses', 'private_addresses'].include? attribute.name)
- obj.add_addresses!(attribute.name, (attribute/'*')) && next
- end
-
- if ('authentication'.include? attribute.name)
- obj.add_authentication!(attribute[:type], (attribute/'*')) && next
- end
-
- #deal with providers
- if(attribute.name == 'provider')
- obj.add_provider!(attribute.attributes['id'].value, (attribute/'entrypoint')) && next
- end
-
- # Deal with collections like public-addresses, private-addresses
- if (attribute/'./*').length > 0
- obj.add_collection!(attribute.name, (attribute/'*').collect { |value| value.text }) && next
- end
-
- #deal with blobs for buckets
- if(attribute.name == 'blob')
- obj.add_blob!(attribute.attributes['id'].value) && next
- end
-
- # Anything else is treaten as text object
- obj.add_text!(attribute.name, attribute.text.convert)
- end
- return obj
- end
-
- # Get /api and parse entry points
- def discover_entry_points
- return if discovered?
- request(:get, @api_uri.to_s) do |response|
- if response.code == 301
- @api_uri = response.headers[:location]
- return discover_entry_points
- end
- api_xml = Nokogiri::XML(response)
- @driver_name = api_xml.xpath('/api').first[:driver]
- @api_version = api_xml.xpath('/api').first[:version]
-
- api_xml.css("api > link").each do |entry_point|
- rel, href = entry_point['rel'].to_sym, entry_point['href']
- @entry_points.store(rel, href)
-
- entry_point.css("feature").each do |feature|
- @features[rel] ||= []
- @features[rel] << feature['name'].to_sym
-
- end
- end
- end
- declare_entry_points_methods(@entry_points)
- end
-
- # Generate create_* methods dynamically
- #
- def method_missing(name, *args)
- if name.to_s =~ /^([\w_]+)_ids$/
- return self.send(:"#{$1.pluralize}").map { |o| o.id }
- end
- if name.to_s =~ /^create_(\w+)/
- params = args[0] if args[0] and args[0].class.eql?(Hash)
- params ||= args[1] if args[1] and args[1].class.eql?(Hash)
- params ||= {}
-
- # FIXME: This fixes are related to Instance model and should be
- # replaced by 'native' parameter names
-
- params[:realm_id] ||= params[:realm] if params[:realm]
- params[:keyname] ||= params[:key_name] if params[:key_name]
- params[:user_data] = Base64::encode64(params[:user_data]) if params[:user_data]
-
- if params[:hardware_profile] and params[:hardware_profile].class.eql?(Hash)
- params[:hardware_profile].each do |k,v|
- params[:"hwp_#{k}"] ||= v
- end
- else
- params[:hwp_id] ||= params[:hardware_profile]
- end
-
- params[:image_id] ||= params[:image_id] || args[0] if args[0].class!=Hash
-
- obj = nil
-
- request(:post, entry_points[:"#{$1}s"], {}, params) do |response|
- obj = base_object(:"#{$1}", response)
- response_error(response) unless response_successful?(response.code)
- yield obj if block_given?
- end
- return obj
- end
- raise NoMethodError
- end
-
- def use_driver(driver, opts={})
- if driver
- @api_driver = driver
- @driver_name = driver
- @api_provider = opts[:provider] if opts[:provider]
- @features, @entry_points = {}, {}
- discover_entry_points
- end
- @username = opts[:username] if opts[:username]
- @password = opts[:password] if opts[:password]
- @api_provider = opts[:provider] if opts[:provider]
- return self
- end
-
- def use_config!(opts={})
- @api_uri = URI.parse(opts[:url]) if opts[:url]
- use_driver(opts[:driver], opts)
- end
-
- def extended_headers
- headers = {}
- headers["X-Deltacloud-Driver"] = @api_driver.to_s if @api_driver
- headers["X-Deltacloud-Provider"] = @api_provider.to_s if @api_provider
- headers
- end
-
- def response_successful?(code)
- return true if code.to_s =~ /^2(\d{2})$/
- return true if code.to_s =~ /^3(\d{2})$/
- return false
- end
-
- def response_error(response)
- xml = Nokogiri::XML(response.to_s)
- if (xml/'message').empty? and response.code.to_s =~ /4(\d{2})/
- DeltaCloud::HTTPError.client_error(response.code)
- else
- opts = {
- :params => (xml/'request/param').inject({}) { |r,p| r[:"#{p[:name]}"] = p.text; r }
- }
- if backend_node = xml.at_xpath('/error/backend')
- opts[:driver] = backend_node[:driver]
- opts[:provider] = backend_node[:provider]
- end
- backtrace = (xml/'backtrace').empty? ? nil : (xml/'backtrace').first.text.split("\n")[1..10].map { |l| l.strip }
- DeltaCloud::HTTPError.server_error(xml.root[:status] || response.code,
- (xml/'message').first.text, opts, backtrace)
- end
- end
-
- # Basic request method
- #
- def request(*args, &block)
- conf = {
- :method => (args[0] || 'get').to_sym,
- :path => (args[1]=~/^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}",
- :query_args => args[2] || {},
- :form_data => args[3] || {},
- :timeout => args[4] || 45,
- :open_timeout => args[5] || 10
- }
- if conf[:query_args] != {}
- conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
- end
-
- if conf[:method].eql?(:post)
- resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout])
- resource.send(:post, conf[:form_data], default_headers.merge(extended_headers)) do |response, request, block|
- response_error(response) unless response_successful? response.code
- yield response.to_s if block_given?
- end
- else
- resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout])
- resource.send(conf[:method], default_headers.merge(extended_headers)) do |response, request, block|
- response_error(response) unless response_successful? response.code
- yield response.to_s if block_given?
- end
- end
- end
-
-
- # Check if specified collection have wanted feature
- def feature?(collection, name)
- @features.has_key?(collection) && @features[collection].include?(name)
- end
-
- # List available instance states and transitions between them
- def instance_states
- states = []
- request(:get, entry_points[:instance_states]) do |response|
- Nokogiri::XML(response).xpath('states/state').each do |state_el|
- state = DeltaCloud::InstanceState::State.new(state_el['name'])
- state_el.xpath('transition').each do |transition_el|
- state.transitions << DeltaCloud::InstanceState::Transition.new(
- transition_el['to'],
- transition_el['action']
- )
- end
- states << state
- end
- end
- states
- end
-
- # Select instance state specified by name
- def instance_state(name)
- instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first
- end
-
- # Skip parsing /api when we already got entry points
- def discovered?
- true if @entry_points!={}
- end
-
- # This method will retrieve API documentation for given collection
- def documentation(collection, operation=nil)
- data = {}
- request(:get, "/docs/#{collection}") do |body|
- document = Nokogiri::XML(body)
- if operation
- data[:operation] = operation
- data[:description] = document.xpath('/docs/collection/operations/operation[@name = "'+operation+'"]/description').first.text.strip
- return false unless data[:description]
- data[:params] = []
- (document/"/docs/collection/operations/operation[@name='#{operation}']/parameter").each do |param|
- data[:params] << {
- :name => param['name'],
- :required => param['type'] == 'optional',
- :type => (param/'class').text
- }
- end
- else
- data[:description] = (document/'/docs/collection/description').text
- data[:collection] = collection
- data[:operations] = (document/"/docs/collection/operations/operation").collect{ |o| o['name'] }
- end
- end
- return Documentation.new(self, data)
- end
-
- private
-
- def default_headers
- # The linebreaks inserted every 60 characters in the Base64
- # encoded header cause problems under JRuby
- auth_header = "Basic "+Base64.encode64("#{@username}:#{@password}")
- auth_header.gsub!("\n", "")
- {
- :authorization => auth_header,
- :accept => "application/xml"
- }
- end
-
- end
-
-end
diff --git a/client/lib/deltacloud/client.rb b/client/lib/deltacloud/client.rb
new file mode 100644
index 0000000..36200ad
--- /dev/null
+++ b/client/lib/deltacloud/client.rb
@@ -0,0 +1,78 @@
+# 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 Client
+ require 'require_relative' if RUBY_VERSION < '1.9'
+ require 'ostruct'
+ require 'nokogiri'
+ require 'faraday'
+
+ # Core extensions
+ require_relative './core_ext'
+
+ # Errors && Helpers
+ require_relative './client/helpers/model_helper'
+ require_relative './client/helpers/xml_helper'
+ require_relative './client/helpers/property_helper'
+
+ # Exceptions goes here
+ require_relative './client/base_error'
+
+ # Faraday Middleware for Deltacloud errors
+ require_relative './error_response'
+
+ # Deltacloud API methods
+ require_relative './client/methods/api'
+ require_relative './client/methods/backward_compatiblity'
+
+ # Extend Client module with methods that existed in old client
+ # and we want to keep them.
+ # Deprecation warnings should be provided to users if they use something
+ # from these modules.
+ #
+ extend Deltacloud::Client::Methods::BackwardCompatibility::ClassMethods
+
+ # Deltacloud methods
+ require_relative './client/methods'
+
+ # Deltacloud models
+ require_relative './client/models'
+
+ require_relative './client/connection'
+
+ VERSION = '1.1.2'
+
+ # Check if the connection to Deltacloud API is valid
+ def self.valid_connection?(api_url)
+ begin
+ Deltacloud::Client(api_url, '', '') && true
+ rescue Faraday::Error::ConnectionFailed
+ false
+ rescue Deltacloud::Client::AuthenticationError
+ false
+ end
+ end
+
+ end
+
+ def self.Client(url, api_user, api_password, opts={})
+ Client::Connection.new({
+ :url => url,
+ :api_user => api_user,
+ :api_password => api_password
+ }.merge(opts))
+ end
+end
diff --git a/client/lib/deltacloud/client/base_error.rb b/client/lib/deltacloud/client/base_error.rb
new file mode 100644
index 0000000..544aa90
--- /dev/null
+++ b/client/lib/deltacloud/client/base_error.rb
@@ -0,0 +1,84 @@
+# 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::Client
+
+ # Reporting internal client errors
+ #
+ class Error < StandardError; end
+
+ class BaseError < Error
+ attr_reader :server_backtrace
+ attr_reader :driver
+ attr_reader :provider
+ attr_reader :status
+
+ def initialize(opts={})
+ if opts.is_a? Hash
+ @server_backtrace = opts[:server_backtrace]
+ @driver = opts[:driver]
+ @provider = opts[:provider]
+ @status = opts[:status]
+ @original_error = opts[:original_error]
+ super(opts[:message])
+ else
+ super(opts)
+ end
+ end
+
+ # Return the original XML error message received from Deltacloud API
+ def original_error
+ @original_error
+ end
+
+ # If the Deltacloud API server error response contain backtrace from
+ # server,then make this backtrace available as part of this exception
+ # backtrace
+ #
+ def set_backtrace(backtrace)
+ return super(backtrace) if @server_backtrace.nil?
+ super([
+ backtrace[0..3],
+ "-------Deltacloud API backtrace-------",
+ @server_backtrace.split[0..10],
+ ].flatten)
+ end
+
+ end
+
+ # Report 401 errors
+ class AuthenticationError < BaseError; end
+
+ # Report 502 errors (back-end cloud provider encounter error)
+ class BackendError < BaseError; end
+
+ # Report 5xx errors (error on Deltacloud API server)
+ class ServerError < BaseError; end
+
+ # Report 501 errors (collection or operation is not supported)
+ class NotSupported < ServerError; end
+
+ # Report 4xx failures (client failures)
+ class ClientFailure < BaseError; end
+
+ # Report 404 error (object not found)
+ class NotFound < BaseError; end
+
+ # Report 405 failures (resource state does not permit the requested operation)
+ class InvalidState < ClientFailure; end
+
+ # Report this when client do Image#launch using incompatible HWP
+ class IncompatibleHardwareProfile < ClientFailure; end
+end
diff --git a/client/lib/deltacloud/client/connection.rb b/client/lib/deltacloud/client/connection.rb
new file mode 100644
index 0000000..ad91cb0
--- /dev/null
+++ b/client/lib/deltacloud/client/connection.rb
@@ -0,0 +1,135 @@
+# 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::Client
+ class Connection
+
+ attr_accessor :connection
+ attr_reader :request_driver
+ attr_reader :request_provider
+ attr_reader :entrypoint
+
+ include Deltacloud::Client::Helpers::Model
+
+ include Deltacloud::Client::Methods::Common
+ include Deltacloud::Client::Methods::Api
+ include Deltacloud::Client::Methods::BackwardCompatibility
+ include Deltacloud::Client::Methods::Driver
+ include Deltacloud::Client::Methods::Realm
+ include Deltacloud::Client::Methods::HardwareProfile
+ include Deltacloud::Client::Methods::Image
+ include Deltacloud::Client::Methods::Instance
+ include Deltacloud::Client::Methods::InstanceState
+ include Deltacloud::Client::Methods::Key
+ include Deltacloud::Client::Methods::StorageVolume
+ include Deltacloud::Client::Methods::StorageSnapshot
+ include Deltacloud::Client::Methods::Address
+ include Deltacloud::Client::Methods::Bucket
+ include Deltacloud::Client::Methods::Blob
+ include Deltacloud::Client::Methods::Firewall
+
+ def initialize(opts={})
+ @request_driver = opts[:driver]
+ @request_provider = opts[:provider]
+ @connection = Faraday.new(:url => opts[:url]) do |f|
+ # NOTE: The order of this is somehow important for VCR
+ # recording.
+ f.request :url_encoded
+ f.headers = deltacloud_request_headers
+ f.basic_auth opts[:api_user], opts[:api_password]
+ f.use Deltacloud::ErrorResponse
+ f.adapter :net_http
+ end
+ cache_entrypoint!
+ @request_driver ||= current_driver
+ @request_provider ||= current_provider
+ end
+
+ # Change the current driver and return copy of the client
+ # This allows chained calls like: client.driver(:ec2).instances
+ #
+ # - driver_id -> The new driver id (:mock, :ec2, :rhevm, ...)
+ # - api_user -> API user name
+ # - api_password -> API password
+ # - api_provider -> API provider (aka API_PROVIDER string)
+ #
+ def use(driver_id, api_user, api_password, api_provider=nil, &block)
+ new_client = self.class.new(
+ :url => @connection.url_prefix.to_s,
+ :api_user => api_user,
+ :api_password => api_password,
+ :provider => api_provider,
+ :driver => driver_id
+ )
+ new_client.cache_entrypoint!
+ yield new_client if block_given?
+ new_client
+ end
+
+ # Change the API provider but keep the current client credentials.
+ # This allows to change the EC2 region and list instances in that
+ # region without need to supply credentials.
+ #
+ # client.use_provider('eu-west-1') { |p| p.instances }
+ #
+ # - provider_id -> API provider (aka API_PROVIDER)
+ #
+ def use_provider(provider_id, &block)
+ new_client = self.clone
+ new_connection = @connection.clone
+ new_connection.headers['X-Deltacloud-Provider'] = provider_id
+ new_client.connection = new_connection
+ new_client.cache_entrypoint!(true)
+ yield new_client if block_given?
+ new_client
+ end
+
+ # Cache the API entrypoint (/api) for the current connection,
+ # so we don't need to query /api everytime we ask if certain
+ # collection/operation is supported
+ #
+ # - force -> If 'true' force to refresh stored cached entrypoint
+ #
+ def cache_entrypoint!(force=false)
+ @entrypoint = nil if force
+ @entrypoint ||= connection.get(path).body
+ end
+
+ # Check if the credentials used are valid for the current @connection
+ #
+ def valid_credentials?
+ begin
+ r = connection.get(path, { :force_auth => 'true' })
+ r.status == 200
+ rescue error(:authentication_error)
+ false
+ end
+ end
+
+ private
+
+ # Default Deltacloud HTTP headers. Common for *all* requests
+ # to Deltacloud API
+ #
+ def deltacloud_request_headers
+ headers = {}
+ headers['Accept'] = 'application/xml'
+ headers['X-Deltacloud-Driver'] = @request_driver.to_s if @request_driver
+ headers['X-Deltacloud-Provider'] = @request_provider.to_s if @request_provider
+ headers
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/client/helpers/model_helper.rb b/client/lib/deltacloud/client/helpers/model_helper.rb
new file mode 100644
index 0000000..2224d0d
--- /dev/null
+++ b/client/lib/deltacloud/client/helpers/model_helper.rb
@@ -0,0 +1,69 @@
+# 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::Client
+ module Helpers
+ module Model
+
+ # Retrieve the class straight from
+ # Deltacloud::Client model.
+ #
+ # -name -> A class name in underscore form (:storage_volume)
+ #
+ def model(name)
+ if name.nil? or (!name.nil? and name.empty?)
+ raise error.new("The model name can't be blank")
+ end
+ Deltacloud::Client.const_get(name.to_s.camelize)
+ end
+
+ # Syntax sugar method for retrieving various Client
+ # exception classes.
+ #
+ # - name -> Exception class name in underscore
+ #
+ # NOTE: If name is 'nil' the default Error exception
+ # will be returned
+ #
+ def error(name=nil)
+ model(name || :error)
+ end
+
+ # Checks if current @connection support +model_name+
+ # and then convert HTTP response to a Ruby model
+ #
+ # - model_name -> A class name in underscore form
+ # - collection_body -> HTTP body of collection
+ #
+ def from_collection(model_name, collection_body)
+ must_support!(model_name)
+ model(model_name.to_s.singularize).from_collection(
+ self,
+ collection_body
+ )
+ end
+
+ # Check if the collection for given model is supported
+ # in current @connection and then parse/convert
+ # resource XML to a Ruby class
+ #
+ def from_resource(model_name, resource_body)
+ must_support!(model_name.to_s.pluralize)
+ model(model_name).convert(self, resource_body)
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/helpers/property_helper.rb b/client/lib/deltacloud/client/helpers/property_helper.rb
new file mode 100644
index 0000000..a30ab9a
--- /dev/null
+++ b/client/lib/deltacloud/client/helpers/property_helper.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::Client
+ module Helpers
+ module Property
+
+ class Property
+ attr_reader :name, :unit, :default
+
+ def initialize(name, unit, default=nil)
+ @name = name
+ @unit = unit
+ @default = default
+ end
+
+ def value
+ @default || 'opaque'
+ end
+
+ def self.parse(body)
+ Property.new(body['name'], body['unit'], body['value'])
+ end
+
+ def kind
+ self.class.name.split('::').last.downcase.to_sym
+ end
+
+ end
+
+ class Range < Property
+
+ attr_reader :first, :last
+
+ def initialize(name, unit, first, last, default=nil)
+ @first, @last = first, last
+ super(name, unit, default)
+ end
+
+ def value
+ ::Range.new(@first.to_i, @last.to_i)
+ end
+
+ def self.parse(body)
+ base = super
+ new(base.name, base.unit, body.at('range')['first'], body.at('range')['last'], base.default)
+ end
+
+ end
+
+ class Enum < Property
+ include Enumerable
+ attr_reader :values
+
+ def initialize(name, unit, values, default=nil)
+ @values = values
+ super(name, unit, default)
+ end
+
+ def value
+ @values
+ end
+
+ def each
+ value.each
+ end
+
+ def self.parse(body)
+ base = super
+ new(base.name, base.unit, body.xpath('enum/entry').map { |e| e['value'] }, base.default)
+ end
+ end
+
+ class Fixed < Property
+ attr_reader :value
+
+ def initialize(name, unit, value)
+ @value = value
+ super(name, unit, @value)
+ end
+
+ def self.parse(body)
+ base = super
+ new(base.name, base.unit, body['value'])
+ end
+
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/helpers/xml_helper.rb b/client/lib/deltacloud/client/helpers/xml_helper.rb
new file mode 100644
index 0000000..5874c00
--- /dev/null
+++ b/client/lib/deltacloud/client/helpers/xml_helper.rb
@@ -0,0 +1,33 @@
+# 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::Client
+ module Helpers
+ module XmlHelper
+
+ # Extract XML string from the various objects
+ #
+ def extract_xml_body(obj)
+ case obj
+ when Faraday::Response then obj.body
+ when Nokogiri::XML::Element then obj.to_s
+ when Nokogiri::XML::Document then obj.to_s
+ else obj
+ end
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods.rb b/client/lib/deltacloud/client/methods.rb
new file mode 100644
index 0000000..0897866
--- /dev/null
+++ b/client/lib/deltacloud/client/methods.rb
@@ -0,0 +1,29 @@
+# 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_relative './methods/common'
+require_relative './methods/driver'
+require_relative './methods/realm'
+require_relative './methods/hardware_profile'
+require_relative './methods/image'
+require_relative './methods/instance'
+require_relative './methods/instance_state'
+require_relative './methods/storage_volume'
+require_relative './methods/storage_snapshot'
+require_relative './methods/key'
+require_relative './methods/address'
+require_relative './methods/bucket'
+require_relative './methods/blob'
+require_relative './methods/firewall'
diff --git a/client/lib/deltacloud/client/methods/address.rb b/client/lib/deltacloud/client/methods/address.rb
new file mode 100644
index 0000000..21da5f6
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/address.rb
@@ -0,0 +1,68 @@
+# 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::Client
+ module Methods
+ module Address
+
+ # Retrieve list of all address entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def addresses(filter_opts={})
+ from_collection :addresses,
+ connection.get(api_uri('addresses'), filter_opts)
+ end
+
+ # Retrieve the single address entity
+ #
+ # - address_id -> Address entity to retrieve
+ #
+ def address(address_id)
+ from_resource :address,
+ connection.get(api_uri("addresses/#{address_id}"))
+ end
+
+ # Create a new address
+ #
+ def create_address
+ create_resource :address, {}
+ end
+
+ def destroy_address(address_id)
+ destroy_resource :address, address_id
+ end
+
+ def associate_address(address_id, instance_id)
+ result = connection.post(
+ api_uri("/addresses/#{address_id}/associate")
+ ) do |request|
+ request.params = { :instance_id => instance_id }
+ end
+ result.status == 202
+ end
+
+ def disassociate_address(address_id)
+ result = connection.post(
+ api_uri("/addresses/#{address_id}/disassociate")
+ )
+ result.status == 202
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/api.rb b/client/lib/deltacloud/client/methods/api.rb
new file mode 100644
index 0000000..32bd048
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/api.rb
@@ -0,0 +1,96 @@
+# 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::Client
+ module Methods
+ module Api
+
+ def path
+ connection.url_prefix.path
+ end
+
+ def api_uri(uri)
+ URI.parse([path, uri.gsub(/^(\/+)/,'')].join('/'))
+ end
+
+ # The current version of Deltacloud API
+ #
+ def version
+ entrypoint.to_xml.root['version']
+ end
+
+ alias_method :api_version, :version
+
+ # The current driver the @connection is using
+ #
+ def current_driver
+ entrypoint.to_xml.root['driver']
+ end
+
+ alias_method :api_driver, :current_driver
+ alias_method :driver_name, :current_driver
+
+ # The current provider the @connection is using
+ #
+ def current_provider
+ entrypoint.to_xml.root['provider']
+ end
+
+ # List of the currently supported collections by @connection
+ #
+ def supported_collections
+ entrypoint.to_xml.root.xpath('link').map { |l| l['rel'] }
+ end
+
+ alias_method :entrypoints, :supported_collections
+
+ # Syntax sugar for +supported_collections+
+ # Return 'true' if the collection is supported by current API entrypoint
+ #
+ def support?(collection)
+ supported_collections.include? collection.to_s
+ end
+
+ # Syntax sugar for Method modules, where you can 'require' the support
+ # for the given collection before you execute API call
+ #
+ # Raise +NotSupported+ exception if the given +collection+ is not
+ # supported
+ #
+ def must_support!(collection)
+ unless support?(collection)
+ raise error(:not_supported).new("Collection '#{collection}' not supported by current API endpoint.")
+ end
+ end
+
+ # +Hash+ of all features supported by current connection
+ #
+ def features
+ entrypoint.to_xml.root.xpath('link/feature').inject(Hash.new(Array.new)) do |result, feature|
+ result[feature.parent['rel']] += [feature['name']]
+ result
+ end
+ end
+
+ # Check if the current collection support given feature for given
+ # collection
+ #
+ def feature?(collection_name, feature_name)
+ features[collection_name.to_s].include?(feature_name.to_s)
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/backward_compatiblity.rb b/client/lib/deltacloud/client/methods/backward_compatiblity.rb
new file mode 100644
index 0000000..8718836
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/backward_compatiblity.rb
@@ -0,0 +1,72 @@
+# 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::Client
+ module Methods
+ module BackwardCompatibility
+
+ # Backward compatibility methods provides fallback for the
+ # old deltacloud-client gem.
+ #
+ #
+ def api_host
+ connection.url_prefix.host
+ end
+
+ def api_port
+ connection.url_prefix.port
+ end
+
+ def connect(&block)
+ yield self.clone
+ end
+
+ def with_config(opts, &block)
+ yield inst = use(
+ opts[:driver],
+ opts[:username],
+ opts[:password],
+ opts[:provider]
+ ) if block_given?
+ inst
+ end
+
+ def use_driver(new_driver, opts={})
+ with_config(opts.merge(:driver => new_driver))
+ end
+
+ alias_method :"use_config!", :use_driver
+
+ def discovered?
+ true unless entrypoint.nil?
+ end
+
+ module ClassMethods
+
+ def valid_credentials?(api_user, api_password, api_url, opts={})
+ args = {
+ :api_user => api_user,
+ :api_password => api_password,
+ :url => api_url
+ }
+ args.merge!(:providers => opts[:provider]) if opts[:provider]
+ Deltacloud::Client::Connection.new(args).valid_credentials?
+ end
+
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/blob.rb b/client/lib/deltacloud/client/methods/blob.rb
new file mode 100644
index 0000000..80a3945
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/blob.rb
@@ -0,0 +1,91 @@
+# 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::Client
+ module Methods
+ module Blob
+
+ # Retrieve list of all blob entities from given bucket
+ #
+ def blobs(bucket_id=nil)
+ raise error.new("The :bucket_id cannot be nil.") if bucket_id.nil?
+ bucket(bucket_id).blob_ids.map { |blob_id| blob(bucket_id, blob_id) }
+ end
+
+ # Retrieve the single blob entity
+ #
+ # - blob_id -> Blob entity to retrieve
+ #
+ def blob(bucket_id, blob_id)
+ model(:blob).convert(
+ self,
+ connection.get(api_uri("buckets/#{bucket_id}/#{blob_id}"))
+ )
+ end
+
+ # Create a new blob
+ #
+ # - bucket_id -> A bucket ID that new blob should belong to
+ # - blob_name -> A name for new blob
+ # - blob_data -> Data stored in this blob
+ # - create_opts
+ # - :user_metadata -> A Ruby +Hash+ with key => value metadata
+ #
+ def create_blob(bucket_id, blob_name, blob_data, create_opts={})
+ must_support! :buckets
+ create_opts.merge!(convert_meta_params(create_opts.delete(:user_metadata)))
+ response = connection.post(api_uri("buckets/#{bucket_id}")) do |request|
+ request.params = create_opts.merge(
+ :blob_id => blob_name,
+ :blob_data => blob_data
+ )
+ end
+ model(:blob).convert(self, response.body)
+ end
+
+ # Destroy given bucket blob
+ #
+ def destroy_blob(bucket_id, blob_id)
+ must_support! :buckets
+ r = connection.delete(api_uri("buckets/#{bucket_id}/#{blob_id}"))
+ r.status == 204
+ end
+
+ private
+
+ # Convert the user_metadata into POST params compatible with
+ # blob creation
+ #
+ # - params -> Simple Ruby +Hash+
+ #
+ # @return { :meta_params => COUNTER, :meta_name1 => '', :meta_value1 => ''}
+ #
+ def convert_meta_params(params)
+ meta_params = {}
+ counter = 0
+ (params || {}).each do |key, value|
+ counter += 1
+ meta_params["meta_name#{counter}"] = key
+ meta_params["meta_value#{counter}"] = value
+ end
+ if counter >= 1
+ meta_params.merge!(:meta_params => counter.to_s)
+ end
+ meta_params
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/bucket.rb b/client/lib/deltacloud/client/methods/bucket.rb
new file mode 100644
index 0000000..b68cb05
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/bucket.rb
@@ -0,0 +1,56 @@
+# 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::Client
+ module Methods
+ module Bucket
+
+ # Retrieve list of all bucket entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def buckets(filter_opts={})
+ from_collection :buckets,
+ connection.get(api_uri('buckets'), filter_opts)
+ end
+
+ # Retrieve the single bucket entity
+ #
+ # - bucket_id -> Bucket entity to retrieve
+ #
+ def bucket(bucket_id)
+ from_resource :bucket,
+ connection.get(api_uri("buckets/#{bucket_id}"))
+ end
+
+ # Create a new bucket
+ #
+ # - create_opts
+ #
+ def create_bucket(name)
+ create_resource :bucket, :name => name
+ end
+
+ # Destroy given bucket
+ #
+ def destroy_bucket(bucket_id)
+ destroy_resource :bucket, bucket_id
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/common.rb b/client/lib/deltacloud/client/methods/common.rb
new file mode 100644
index 0000000..250e3a4
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/common.rb
@@ -0,0 +1,46 @@
+# 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::Client
+ module Methods
+ module Common
+
+ # A generic method for creating a new resources
+ #
+ # - resource_name -> A resource name to create (eg. :image)
+ # - create_opts -> HTTP options to pass into the create operation
+ #
+ def create_resource(resource_name, create_opts={})
+ no_convert_model = create_opts.delete(:no_convert_model)
+ must_support! resource_name.to_s.pluralize
+ response = connection.post(api_uri(resource_name.to_s.pluralize)) do |request|
+ request.params = create_opts
+ end
+ no_convert_model ? response : model(resource_name).convert(self, response.body)
+ end
+
+ # A generic method for destroying resources
+ #
+ def destroy_resource(resource_name, resource_id)
+ must_support! resource_name.to_s.pluralize
+ result = connection.delete(
+ api_uri([resource_name.to_s.pluralize, resource_id].join('/'))
+ )
+ result.status.is_no_content?
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/driver.rb b/client/lib/deltacloud/client/methods/driver.rb
new file mode 100644
index 0000000..107bcc3
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/driver.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::Client
+ module Methods
+ module Driver
+
+ # Retrieve list of all drivers
+ #
+ # Filter options:
+ #
+ # - :id -> Filter drivers using their 'id'
+ # - :state -> Filter drivers by their 'state'
+ #
+ def drivers(filter_opts={})
+ from_collection(
+ :drivers,
+ connection.get(api_uri('drivers'), filter_opts)
+ )
+ end
+
+ # Retrieve the given driver
+ #
+ # - driver_id -> Driver to retrieve
+ #
+ def driver(driver_id)
+ from_resource(
+ :driver,
+ connection.get(api_uri("drivers/#{driver_id}"))
+ )
+ end
+
+ # List of the current driver providers
+ #
+ def providers
+ driver(current_driver).providers
+ end
+
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/firewall.rb b/client/lib/deltacloud/client/methods/firewall.rb
new file mode 100644
index 0000000..547dfc5
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/firewall.rb
@@ -0,0 +1,66 @@
+# 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::Client
+ module Methods
+ module Firewall
+
+ # Retrieve list of all firewall entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def firewalls(filter_opts={})
+ from_collection :firewalls,
+ connection.get(api_uri('firewalls'), filter_opts)
+ end
+
+ # Retrieve the single firewall entity
+ #
+ # - firewall_id -> Firewall entity to retrieve
+ #
+ def firewall(firewall_id)
+ from_resource :firewall,
+ connection.get(api_uri("firewalls/#{firewall_id}"))
+ end
+
+ # Create a new firewall
+ #
+ # - create_opts
+ #
+ def create_firewall(name, create_opts={})
+ create_resource :firewall, { :name => name }.merge(create_opts)
+ end
+
+ def destroy_firewall(firewall_id)
+ destroy_resource :firewall, firewall_id
+ end
+
+ def add_firewall_rule(firewall_id, protocol, port_from, port_to, opts={})
+ r = connection.post(api_uri("firewalls/#{firewall_id}/rules")) do |request|
+ request.params = {
+ :protocol => protocol,
+ :port_from => port_from,
+ :port_to => port_to
+ }
+ # TODO: Add support for sources
+ end
+ model(:firewall).convert(self, r.body)
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/hardware_profile.rb b/client/lib/deltacloud/client/methods/hardware_profile.rb
new file mode 100644
index 0000000..0cf744c
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/hardware_profile.rb
@@ -0,0 +1,42 @@
+# 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::Client
+ module Methods
+ module HardwareProfile
+
+ # Retrieve list of all hardware_profiles
+ #
+ # Filter options:
+ #
+ # - :id -> Filter hardware_profiles using their 'id'
+ #
+ def hardware_profiles(filter_opts={})
+ from_collection :hardware_profiles,
+ connection.get(api_uri('hardware_profiles'), filter_opts)
+ end
+
+ # Retrieve the given hardware_profile
+ #
+ # - hardware_profile_id -> hardware_profile to retrieve
+ #
+ def hardware_profile(hwp_id)
+ from_resource :hardware_profile,
+ connection.get(api_uri("hardware_profiles/#{hwp_id}"))
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/image.rb b/client/lib/deltacloud/client/methods/image.rb
new file mode 100644
index 0000000..8e3765b
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/image.rb
@@ -0,0 +1,62 @@
+# 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::Client
+ module Methods
+ module Image
+
+ # Retrieve list of all images
+ #
+ # Filter options:
+ #
+ # - :id -> Filter images using their 'id'
+ # - :state -> Filter images by their 'state'
+ # - :architecture -> Filter images by their 'architecture'
+ #
+ def images(filter_opts={})
+ from_collection :images,
+ connection.get(api_uri('images'), filter_opts)
+ end
+
+ # Retrieve the given image
+ #
+ # - image_id -> Image to retrieve
+ #
+ def image(image_id)
+ from_resource :image,
+ connection.get(api_uri("images/#{image_id}"))
+ end
+
+ # Create a new image from instance
+ #
+ # - instance_id -> The stopped instance used for creation
+ # - create_opts
+ # - :name -> Name of the new image
+ # - :description -> Description of the new image
+ #
+ def create_image(instance_id, create_opts={})
+ create_resource :image, { :instance_id => instance_id }.merge(create_opts)
+ end
+
+ # Destroy given image
+ # NOTE: This operation might not be supported for all drivers.
+ #
+ def destroy_image(image_id)
+ destroy_resource :image, image_id
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/instance.rb b/client/lib/deltacloud/client/methods/instance.rb
new file mode 100644
index 0000000..4749d82
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/instance.rb
@@ -0,0 +1,140 @@
+# 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::Client
+ module Methods
+ module Instance
+
+ # Retrieve list of all instances
+ #
+ # Filter options:
+ #
+ # - :id -> Filter instances using their 'id'
+ # - :state -> Filter instances by their 'state'
+ # - :realm_id -> Filter instances based on their 'realm_id'
+ #
+ def instances(filter_opts={})
+ from_collection(
+ :instances,
+ connection.get(api_uri('/instances'), filter_opts)
+ )
+ end
+
+ # Retrieve the given instance
+ #
+ # - instance_id -> Instance to retrieve
+ #
+ def instance(instance_id)
+ from_resource(
+ :instance,
+ connection.get(api_uri("instances/#{instance_id}"))
+ )
+ end
+
+ # Create a new instance
+ #
+ # - image_id -> Image to use for instance creation (img1, ami-12345, etc...)
+ # - create_opts -> Various options that DC support for the current
+ # provider.
+ #
+ # Returns created instance, or list of created instances or all instances.
+ #
+ def create_instance(image_id, create_opts={})
+ r = create_resource :instance, create_opts.merge(
+ :image_id => image_id,
+ :no_convert_model => true
+ )
+ parse_create_instance(r)
+ end
+
+ # Destroy the current +Instance+
+ # Returns 'true' if the response was 204 No Content
+ #
+ # - instance_id -> The 'id' of the Instance to destroy
+ #
+ def destroy_instance(instance_id)
+ destroy_resource :instance, instance_id
+ end
+
+ # Attempt to change the +Instance+ state to STOPPED
+ #
+ # - instance_id -> The 'id' of the Instance to stop
+ #
+ def stop_instance(instance_id)
+ instance_action :stop, instance_id
+ end
+
+ # Attempt to change the +Instance+ state to STARTED
+ #
+ # - instance_id -> The 'id' of the Instance to start
+ #
+ def start_instance(instance_id)
+ instance_action :start, instance_id
+ end
+
+ # Attempt to reboot the +Instance+
+ #
+ # - instance_id -> The 'id' of the Instance to reboot
+ #
+ def reboot_instance(instance_id)
+ instance_action :reboot, instance_id
+ end
+
+ private
+
+ # Avoid codu duplication ;-)
+ #
+ def instance_action(action, instance_id)
+ result = connection.post(
+ api_uri("/instances/#{instance_id}/#{action}")
+ )
+ if result.status.is_ok?
+ from_resource(:instance, result)
+ else
+ instance(instance_id)
+ end
+ end
+
+ # Handles parsing of +create_instance+ method
+ #
+ # - response -> +create_instance+ HTTP response body
+ #
+ def parse_create_instance(response)
+ # If Deltacloud API return only Location (30x), follow it and
+ # retrieve created instance from there.
+ #
+ if response.status.is_redirect?
+ # If Deltacloud API redirect to list of instances
+ # then return list of **all** instances, otherwise
+ # grab the instance_id from Location header
+ #
+ redirect_instance = response.headers['Location'].split('/').last
+ if redirect_instance == 'instances'
+ instances
+ else
+ instance(redirect_instance)
+ end
+ elsif response.body.to_xml.root.name == 'instances'
+ # If more than 1 instance was created, return list
+ #
+ from_collection(:instances, response.body)
+ else
+ from_resource(:instance, response)
+ end
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/instance_state.rb b/client/lib/deltacloud/client/methods/instance_state.rb
new file mode 100644
index 0000000..3c853d0
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/instance_state.rb
@@ -0,0 +1,41 @@
+# 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::Client
+ module Methods
+ module InstanceState
+
+ # Representation of the current driver state machine
+ #
+ def instance_states
+ r = connection.get(api_uri("instance_states"))
+ r.body.to_xml.root.xpath('state').map do |se|
+ state = model(:instance_state).new_state(se['name'])
+ se.xpath('transition').each do |te|
+ state.transitions << model(:instance_state).new_transition(
+ te['to'], te['action']
+ )
+ end
+ state
+ end
+ end
+
+ def instance_state(name)
+ instance_states.find { |s| s.name.to_s.eql?(name.to_s) }
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/key.rb b/client/lib/deltacloud/client/methods/key.rb
new file mode 100644
index 0000000..8984f23
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/key.rb
@@ -0,0 +1,59 @@
+# 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::Client
+ module Methods
+ module Key
+
+ # Retrieve list of all key entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def keys(filter_opts={})
+ from_collection :keys,
+ connection.get(api_uri('keys'), filter_opts)
+ end
+
+ # Retrieve the single key entity
+ #
+ # - key_id -> Key entity to retrieve
+ #
+ def key(key_id)
+ from_resource :key,
+ connection.get(api_uri("keys/#{key_id}"))
+ end
+
+ # Create a new credentials to use with authentication
+ # to an +Instance+
+ #
+ # - key_name -> The name of the key
+ # - create_opts
+ # : public_key -> Your SSH public key (eg. ~/.ssh/id_rsa.pub)
+ #
+ def create_key(key_name, create_opts={})
+ create_resource :key, create_opts.merge(:name => key_name)
+ end
+
+ # Destroy the SSH key
+ #
+ def destroy_key(key_id)
+ destroy_resource :key, key_id
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/realm.rb b/client/lib/deltacloud/client/methods/realm.rb
new file mode 100644
index 0000000..41807f0
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/realm.rb
@@ -0,0 +1,43 @@
+# 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::Client
+ module Methods
+ module Realm
+
+ # Retrieve list of all realms
+ #
+ # Filter options:
+ #
+ # - :id -> Filter realms using their 'id'
+ # - :state -> Filter realms by their 'state'
+ #
+ def realms(filter_opts={})
+ from_collection :realms,
+ connection.get(api_uri("realms"), filter_opts)
+ end
+
+ # Retrieve the given realm
+ #
+ # - realm_id -> Instance to retrieve
+ #
+ def realm(realm_id)
+ from_resource :realm,
+ connection.get(api_uri("realms/#{realm_id}"))
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/storage_snapshot.rb b/client/lib/deltacloud/client/methods/storage_snapshot.rb
new file mode 100644
index 0000000..33c1696
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/storage_snapshot.rb
@@ -0,0 +1,62 @@
+# 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::Client
+ module Methods
+ module StorageSnapshot
+
+ # Retrieve list of all storage_snapshot entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def storage_snapshots(filter_opts={})
+ from_collection :storage_snapshots,
+ connection.get(api_uri('storage_snapshots'), filter_opts)
+ end
+
+ # Retrieve the single storage_snapshot entity
+ #
+ # - storage_snapshot_id -> StorageSnapshot entity to retrieve
+ #
+ def storage_snapshot(storage_snapshot_id)
+ from_resource :storage_snapshot,
+ connection.get(api_uri("storage_snapshots/#{storage_snapshot_id}"))
+ end
+
+ # Create a new StorageSnapshot based on +volume_id+
+ #
+ # - volume_id -> ID of the +StorageVolume+ to create snapshot from
+ # - create_opts ->
+ # :name -> Name of the StorageSnapshot
+ # :description -> Description of the StorageSnapshot
+ #
+ def create_storage_snapshot(volume_id, create_opts={})
+ create_resource :storage_snapshot, create_opts.merge(:volume_id => volume_id)
+ end
+
+ # Destroy the current +StorageSnapshot+
+ # Returns 'true' if the response was 204 No Content
+ #
+ # - snapshot_id -> The 'id' of the snapshot to destroy
+ #
+ def destroy_storage_snapshot(snapshot_id)
+ destroy_resource :storage_snapshot, snapshot_id
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/methods/storage_volume.rb b/client/lib/deltacloud/client/methods/storage_volume.rb
new file mode 100644
index 0000000..be6c4ca
--- /dev/null
+++ b/client/lib/deltacloud/client/methods/storage_volume.rb
@@ -0,0 +1,95 @@
+# 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::Client
+ module Methods
+ module StorageVolume
+
+ # Retrieve list of all storage_volumes
+ #
+ # Filter options:
+ #
+ # - :id -> Filter storage_volumes using their 'id'
+ # - :state -> Filter storage_volumes by their 'state'
+ #
+ def storage_volumes(filter_opts={})
+ from_collection :storage_volumes,
+ connection.get(api_uri("storage_volumes"), filter_opts)
+ end
+
+ # Retrieve the given storage_volume
+ #
+ # - storage_volume_id -> Instance to retrieve
+ #
+ def storage_volume(storage_volume_id)
+ from_resource :storage_volume,
+ connection.get(api_uri("storage_volumes/#{storage_volume_id}"))
+ end
+
+ # Create new storage volume
+ #
+ # - :snapshot_id -> Snapshot to use for creating a new volume
+ # - :capacity -> Initial Volume capacity
+ # - :realm_id -> Create volume in this realm
+ # - :name -> Volume name
+ # - :description -> Volume description
+ #
+ # NOTE: Some create options might not be supported by backend cloud
+ #
+ def create_storage_volume(create_opts={})
+ create_resource :storage_volume, create_opts
+ end
+
+ # Destroy the current +StorageVolume+
+ # Returns 'true' if the response was 204 No Content
+ #
+ # - volume_id -> The 'id' of the volume to destroy
+ #
+ def destroy_storage_volume(volume_id)
+ destroy_resource :storage_volume, volume_id
+ end
+
+ # Attach the Storage Volume to the Instance
+ # The +device+ parameter could be used if supported.
+ #
+ # - volume_id -> Volume ID (eg. 'vol1')
+ # - instance_id -> Target Instance ID (eg. 'inst1')
+ # - device -> Target device in Instance (eg. '/dev/sda2')
+ #
+ def attach_storage_volume(volume_id, instance_id, device=nil)
+ must_support! :storage_volumes
+ result = connection.post(api_uri("/storage_volumes/#{volume_id}/attach")) do |r|
+ r.params = { :instance_id => instance_id, :device => device }
+ end
+ if result.status.is_ok?
+ from_resource(:storage_volume, result)
+ end
+ end
+
+ # Detach the Storage Volume from the Instance
+ #
+ # -volume_id -> Volume to detach
+ #
+ def detach_storage_volume(volume_id)
+ must_support! :storage_volumes
+ result = connection.post(api_uri("/storage_volumes/#{volume_id}/detach"))
+ if result.status.is_ok?
+ from_resource(:storage_volume, result)
+ end
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models.rb b/client/lib/deltacloud/client/models.rb
new file mode 100644
index 0000000..63b5a24
--- /dev/null
+++ b/client/lib/deltacloud/client/models.rb
@@ -0,0 +1,30 @@
+# 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_relative './models/base'
+require_relative './models/driver'
+require_relative './models/realm'
+require_relative './models/hardware_profile'
+require_relative './models/image'
+require_relative './models/instance_address'
+require_relative './models/instance'
+require_relative './models/instance_state'
+require_relative './models/storage_volume'
+require_relative './models/storage_snapshot'
+require_relative './models/key'
+require_relative './models/address'
+require_relative './models/bucket'
+require_relative './models/blob'
+require_relative './models/firewall'
diff --git a/client/lib/deltacloud/client/models/address.rb b/client/lib/deltacloud/client/models/address.rb
new file mode 100644
index 0000000..d39d80b
--- /dev/null
+++ b/client/lib/deltacloud/client/models/address.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::Client
+ class Address < Base
+ include Deltacloud::Client::Methods::Address
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ attr_reader :ip
+ attr_reader :instance_id
+
+ # Address model methods
+ #
+
+ # Associate the IP address to the +Instance+
+ #
+ def associate(instance_id)
+ associate_address(_id, instance_id)
+ end
+
+ # Disassociate the IP address from +Instance+
+ #
+ def disassociate
+ disassociate_address(_id)
+ end
+
+ def destroy!
+ destroy_address(_id)
+ end
+
+ # Parse the Address entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the address
+ #
+ def self.parse(xml_body)
+ {
+ :ip => xml_body.text_at(:ip),
+ :instance_id => xml_body.attr_at('instance', :id)
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/base.rb b/client/lib/deltacloud/client/models/base.rb
new file mode 100644
index 0000000..9f51fb0
--- /dev/null
+++ b/client/lib/deltacloud/client/models/base.rb
@@ -0,0 +1,151 @@
+# 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::Client
+
+ class Base
+
+ extend Helpers::XmlHelper
+
+ include Deltacloud::Client::Helpers::Model
+ include Deltacloud::Client::Methods::Api
+
+ # These attributes are common for all models
+ #
+ # - obj_id -> The :id of Deltacloud API model (eg. instance ID)
+ #
+ attr_reader :obj_id
+ attr_reader :name
+ attr_reader :description
+
+ # The Base class that other models should inherit from
+ # To initialize, you need to suply these mandatory params:
+ #
+ # - :_client -> Reference to Client instance
+ # - :_id -> The 'id' of resource. The '_' is there to avoid conflicts
+ #
+ def initialize(opts={})
+ @options = opts
+ @obj_id = @options.delete(:_id)
+ # Do not allow to modify the object#base_id
+ @obj_id.freeze
+ @client = @options.delete(:_client)
+ @original_body = @options.delete(:original_body)
+ update_instance_variables!(@options)
+ end
+
+ alias_method :_id, :obj_id
+
+ # Populate instance variables in model
+ # This method could be also used to update the variables for already
+ # initialized models. Look at +Instance#reload!+ method.
+ #
+ def update_instance_variables!(opts={})
+ @options.merge!(opts)
+ @options.each { |key, val| self.instance_variable_set("@#{key}", val) unless val.nil? }
+ self
+ end
+
+ # Eye-candy representation of model, without ugly @client representation
+ #
+ def to_s
+ "#<#{self.class.name}> #{@options.merge(:_id => @obj_id).inspect}"
+ end
+
+ # An internal reference to the current Deltacloud::Client::Connection
+ # instance. Used for implementing the model methods
+ #
+ def client
+ @client
+ end
+
+ # Shorthand for +client+.connection
+ #
+ # Return Faraday connection object.
+ #
+ def connection
+ client.connection
+ end
+
+ # Return the cached version of Deltacloud API entrypoint
+ #
+ def entrypoint
+ client.entrypoint
+ end
+
+ # Return the original XML body model was constructed from
+ # This might help debugging broken XML
+ #
+ def original_body
+ @original_body
+ end
+
+ # The model#id is the old way how to get the Deltacloud API resource
+ # 'id'. However this collide with the Ruby Object#id.
+ #
+ def id
+ warn '[DEPRECATION] `id` is deprecated because of possible conflict with Object#id. Use `_id` instead.'
+ _id
+ end
+
+ class << self
+
+ # Parse the XML response body from Deltacloud API
+ # to +Hash+. Result is then used to create an instance of Deltacloud model
+ #
+ # NOTE: Children classes **must** implement this class method
+ #
+ def parse(client_ref, inst)
+ warn "The self#parse method **must** be defined in #{self.class.name}"
+ {}
+ end
+
+ # Convert the parsed +Hash+ from +parse+ method to instance of Deltacloud model
+ #
+ # - client_ref -> Reference to the Client instance
+ # - obj -> Might be a Nokogiri::Element or Response
+ #
+ def convert(client_ref, obj)
+ body = extract_xml_body(obj).to_xml.root
+ attrs = parse(body)
+ attrs.merge!({
+ :_id => body['id'],
+ :_client => client_ref,
+ :name => body.text_at(:name),
+ :description => body.text_at(:description)
+ })
+ validate_attrs!(attrs)
+ new(attrs.merge(:original_body => obj))
+ end
+
+ # Convert response for the collection responses.
+ #
+ def from_collection(client_ref, response)
+ response.body.to_xml.xpath('/*/*').map do |entity|
+ convert(client_ref, entity)
+ end
+ end
+
+ # The :_id and :_client attributes are mandotory
+ # to construct a Base model object.
+ #
+ def validate_attrs!(attrs)
+ raise error.new('The :_id must not be nil.') if attrs[:_id].nil?
+ raise error.new('The :_client reference is missing.') if attrs[:_client].nil?
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/blob.rb b/client/lib/deltacloud/client/models/blob.rb
new file mode 100644
index 0000000..569c187
--- /dev/null
+++ b/client/lib/deltacloud/client/models/blob.rb
@@ -0,0 +1,56 @@
+# 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::Client
+ class Blob < Base
+
+ include Deltacloud::Client::Methods::Blob
+ include Deltacloud::Client::Methods::Bucket
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ attr_reader :bucket_id
+ attr_reader :content_length
+ attr_reader :content_type
+ attr_reader :last_modified
+ attr_reader :user_metadata
+
+ # Blob model methods
+ #
+
+ def bucket
+ super(bucket_id)
+ end
+
+ # Parse the Blob entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the blob
+ #
+ def self.parse(xml_body)
+ {
+ :bucket_id => xml_body.text_at(:bucket_id) || xml_body.text_at(:bucket), # FIXME: DC bug
+ :content_length => xml_body.text_at(:content_length),
+ :content_type => xml_body.text_at(:content_type),
+ :last_modified => xml_body.text_at(:last_modified),
+ :user_metadata => xml_body.xpath('user_metadata/entry').inject({}) { |r,e|
+ r[e['key']] = e.text.strip
+ r
+ }
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/bucket.rb b/client/lib/deltacloud/client/models/bucket.rb
new file mode 100644
index 0000000..9a8a856
--- /dev/null
+++ b/client/lib/deltacloud/client/models/bucket.rb
@@ -0,0 +1,65 @@
+# 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::Client
+ class Bucket < Base
+
+ include Deltacloud::Client::Methods::Bucket
+ include Deltacloud::Client::Methods::Blob
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ attr_reader :size
+ attr_reader :blob_ids
+
+ # Bucket model methods
+ #
+ #
+
+ # All blobs associated with the current bucket
+ # The 'bucket_id' should not be set in this case.
+ #
+ def blobs(bucket_id=nil)
+ super(_id)
+ end
+
+ # Add a new blob to the bucket.
+ # See: +create_blob+
+ #
+ def add_blob(blob_name, blob_data, blob_create_opts={})
+ create_blob(_id, blob_name, blob_data, create_opts)
+ end
+
+ # Remove a blob from the bucket
+ # See: +destroy_blob+
+ #
+ def remove_blob(blob_id)
+ destroy_blob(_id, blob_id)
+ end
+
+ # Parse the Bucket entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the bucket
+ #
+ def self.parse(xml_body)
+ {
+ :size => xml_body.text_at(:size),
+ :blob_ids => xml_body.xpath('blob').map { |b| b['id'] }
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/driver.rb b/client/lib/deltacloud/client/models/driver.rb
new file mode 100644
index 0000000..538d463
--- /dev/null
+++ b/client/lib/deltacloud/client/models/driver.rb
@@ -0,0 +1,87 @@
+# 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::Client
+
+ class Driver < Base
+
+ attr_reader :providers
+
+ # Syntax sugar for returning provider from Driver
+ # instance:
+ #
+ # client.driver(:ec2)['us-west-1'] # => List of endpoints
+ #
+ # - provider_id -> Provider ID, like 'us-west-1'
+ #
+ def [](provider_id)
+ @providers ||= []
+ prov = @providers.find { |p| p.name == provider_id }
+ prov.instance_variable_set('@client', @client)
+ prov
+ end
+
+ def self.parse(xml_body)
+ {
+ :providers => xml_body.xpath('provider').map { |p| Provider.parse(p) }
+ }
+ end
+
+ class Provider
+
+ attr_reader :name
+ attr_reader :entrypoints
+
+ def initialize(name, entrypoints=[])
+ @name = name
+ @entrypoints = entrypoints
+ end
+
+ # Syntax sugar for retrieving list of endpoints available for the
+ # provider
+ #
+ # - entrypoint_id -> Entrypoint ID, like 's3'
+ #
+ def [](entrypoint_id)
+ @entrypoints ||= []
+ ent_point = @entrypoints.find { |name, _| name == entrypoint_id }
+ ent_point ? ent_point.last : nil
+ end
+
+ # Method to check if given credentials can be used to authorize
+ # connection to current provider:
+ #
+ # client.driver(:ec2)['us-west-1'].valid_credentials? 'user', 'password'
+ #
+ # - api_user -> API key
+ # - api_password -> API secret
+ #
+ def valid_credentials?(api_user, api_password)
+ unless @client
+ raise error.new('Please use driver("ec2")[API_PROVIDER].valid_credentials?')
+ end
+ @client.use(@client.current_driver, api_user, api_password, @name).valid_credentials?
+ end
+
+ def self.parse(p)
+ new(
+ p['id'],
+ p.xpath('entrypoint').inject({}) { |r, e| r[e['kind']] = e.text.strip; r }
+ )
+ end
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/client/models/firewall.rb b/client/lib/deltacloud/client/models/firewall.rb
new file mode 100644
index 0000000..9bbe0f2
--- /dev/null
+++ b/client/lib/deltacloud/client/models/firewall.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::Client
+ class Firewall < Base
+
+ include Deltacloud::Client::Methods::Common
+ include Deltacloud::Client::Methods::Firewall
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ attr_reader :owner_id
+ attr_reader :rules
+
+ # Firewall model methods
+ #
+ # def reboot!
+ # firewall_reboot(_id)
+ # end
+
+ # Parse the Firewall entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the firewall
+ #
+ def self.parse(xml_body)
+ {
+ :owner_id => xml_body.text_at(:owner_id),
+ :rules => xml_body.xpath('rules/rule').map { |rule|
+ Rule.convert(self, rule)
+ }
+ }
+ end
+
+ class Rule < Deltacloud::Client::Base
+
+ attr_reader :allow_protocol
+ attr_reader :port_from
+ attr_reader :port_to
+ attr_reader :direction
+ attr_reader :sources
+
+ def self.parse(xml_body)
+ {
+ :allow_protocol => xml_body.text_at(:allow_protocol),
+ :port_from => xml_body.text_at(:port_from),
+ :port_to => xml_body.text_at(:port_to),
+ :direction => xml_body.text_at(:direction),
+ :sources => xml_body.xpath('sources/source').map { |s|
+ { :name => s['name'], :owner => s['owner'], :type => s['type'] }
+ }
+ }
+ end
+
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/hardware_profile.rb b/client/lib/deltacloud/client/models/hardware_profile.rb
new file mode 100644
index 0000000..83432b8
--- /dev/null
+++ b/client/lib/deltacloud/client/models/hardware_profile.rb
@@ -0,0 +1,68 @@
+# 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::Client
+ class HardwareProfile < Base
+
+ include Deltacloud::Client::Helpers
+
+ attr_reader :properties
+
+ def self.parse(hwp)
+ {
+ :properties => hwp.xpath('property').map { |p|
+ property_klass(p['kind']).parse(p)
+ }
+ }
+ end
+
+ def cpu
+ property :cpu
+ end
+
+ def memory
+ property :memory
+ end
+
+ def architecture
+ property :architecture
+ end
+
+ def storage
+ property :storage
+ end
+
+ def opaque?
+ @properties.empty? && @name == 'opaque'
+ end
+
+ private
+
+ def property(name)
+ @properties.find { |p| p.name == name.to_s }
+ end
+
+ def self.property_klass(kind)
+ case kind
+ when 'enum' then Property::Enum
+ when 'range' then Property::Range
+ when 'fixed' then Property::Fixed
+ when 'opaque' then Property::Property
+ else raise error.new("Unknown HWP property: #{kind}")
+ end
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/client/models/image.rb b/client/lib/deltacloud/client/models/image.rb
new file mode 100644
index 0000000..8599f41
--- /dev/null
+++ b/client/lib/deltacloud/client/models/image.rb
@@ -0,0 +1,60 @@
+# 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::Client
+ class Image < Base
+
+ attr_reader :owner_id, :architecture, :state
+ attr_reader :creation_time, :root_type, :hardware_profile_ids
+
+ def hardware_profiles
+ @client.hardware_profiles.select { |hwp| @hardware_profile_ids.include?(hwp._id) }
+ end
+
+ def is_compatible?(hardware_profile_id)
+ hardware_profile_ids.include? hardware_profile_id
+ end
+
+ # Launch the image using +Instance+#+create_instance+ method.
+ # This method is more strict in checking +HardwareProfile+
+ # and in case you use incompatible HWP it raise an error.
+ #
+ # - create_instance_opts -> +create_instance+ options
+ #
+ def launch(create_instance_opts={})
+
+ if hwp_id = create_instance_opts[:hwp_id]
+ raise error(:incompatible_hardware_profile).new(
+ "Profile '#{hwp_id}' is not compatible with this image."
+ ) unless is_compatible?(hwp_id)
+ end
+
+ @client.create_instance(self._id, create_instance_opts)
+ end
+
+ def self.parse(xml_body)
+ {
+ :owner_id => xml_body.text_at(:owner_id),
+ :architecture => xml_body.text_at(:architecture),
+ :state => xml_body.text_at(:state),
+ :creation_time => xml_body.text_at('creation_time'),
+ :root_type => xml_body.text_at('root_type'),
+ :hardware_profile_ids => xml_body.xpath('hardware_profiles/hardware_profile').map { |h|
+ h['id']
+ }
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/instance.rb b/client/lib/deltacloud/client/models/instance.rb
new file mode 100644
index 0000000..01e1882
--- /dev/null
+++ b/client/lib/deltacloud/client/models/instance.rb
@@ -0,0 +1,122 @@
+# 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::Client
+ class Instance < Base
+
+ include Deltacloud::Client::Methods::Common
+ include Deltacloud::Client::Methods::Instance
+ include Deltacloud::Client::Methods::Realm
+ include Deltacloud::Client::Methods::HardwareProfile
+ include Deltacloud::Client::Methods::Image
+
+ attr_reader :realm_id
+ attr_reader :owner_id
+ attr_reader :image_id
+ attr_reader :hardware_profile_id
+
+ attr_accessor :state
+ attr_accessor :public_addresses
+ attr_accessor :private_addresses
+
+ # Destroy the current Instance
+ #
+ def destroy!
+ destroy_instance(_id)
+ end
+
+ # Execute +stop_instance+ method on current Instance
+ #
+ def stop!
+ stop_instance(_id) && reload!
+ end
+
+ # Execute +start_instance+ method on current Instance
+ #
+ def start!
+ start_instance(_id) && reload!
+ end
+
+ # Execute +reboot_instance+ method on current Instance
+ #
+ def reboot!
+ reboot_instance(_id) && reload!
+ end
+
+ # Retrieve the +Realm+ associated with Instance
+ #
+ def realm
+ super(realm_id)
+ end
+
+ def hardware_profile
+ super(hardware_profile_id)
+ end
+
+ def image
+ super(image_id)
+ end
+
+ def method_missing(name, *args)
+ return self.state.downcase == $1 if name.to_s =~ /^is_(\w+)\?$/
+ super
+ end
+
+ # Helper for is_STATE?
+ #
+ # is_running?
+ # is_stopped?
+ #
+ def method_missing(name, *args)
+ if name =~ /^is_(\w+)\?$/
+ return state == $1.upcase
+ end
+ super
+ end
+
+ class << self
+
+ def parse(xml_body)
+ {
+ :state => xml_body.text_at('state'),
+ :owner_id => xml_body.text_at('owner_id'),
+ :realm_id => xml_body.attr_at('realm', :id),
+ :image_id => xml_body.attr_at('image', :id),
+ :hardware_profile_id => xml_body.attr_at('hardware_profile', :id),
+ :public_addresses => InstanceAddress.convert(
+ xml_body.xpath('public_addresses/address')
+ ),
+ :private_addresses => InstanceAddress.convert(
+ xml_body.xpath('private_addresses/address')
+ )
+ }
+ end
+
+ end
+
+ # Attempt to reload :public_addresses, :private_addresses and :state
+ # of the instance, after the instance is modified by calling method
+ #
+ def reload!
+ new_instance = instance(_id)
+ update_instance_variables!(
+ :public_addresses => new_instance.public_addresses,
+ :private_addresses => new_instance.private_addresses,
+ :state => new_instance.state
+ )
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/client/models/instance_address.rb b/client/lib/deltacloud/client/models/instance_address.rb
new file mode 100644
index 0000000..f40c264
--- /dev/null
+++ b/client/lib/deltacloud/client/models/instance_address.rb
@@ -0,0 +1,35 @@
+# 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::Client
+ class InstanceAddress < OpenStruct
+
+ attr_reader :type
+ attr_reader :value
+
+ def to_s
+ @value
+ end
+
+ def self.convert(address_xml_block)
+ address_xml_block.map do |addr|
+ new(
+ :type => addr['type'],
+ :value => addr.text
+ )
+ end
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/instance_state.rb b/client/lib/deltacloud/client/models/instance_state.rb
new file mode 100644
index 0000000..79dddad
--- /dev/null
+++ b/client/lib/deltacloud/client/models/instance_state.rb
@@ -0,0 +1,52 @@
+# 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::Client
+
+ class InstanceState
+
+ def self.new_state(name)
+ State.new(name)
+ end
+
+ def self.new_transition(to, action)
+ Transition.new(to, action)
+ end
+
+ class State
+ attr_reader :name
+ attr_reader :transitions
+
+ def initialize(name)
+ @name, @transitions = name, []
+ end
+ end
+
+ class Transition
+ attr_reader :to
+ attr_reader :action
+
+ def initialize(to, action)
+ @to = to
+ @action = action
+ end
+
+ def auto?
+ @action.nil?
+ end
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/client/models/key.rb b/client/lib/deltacloud/client/models/key.rb
new file mode 100644
index 0000000..da38ee5
--- /dev/null
+++ b/client/lib/deltacloud/client/models/key.rb
@@ -0,0 +1,52 @@
+# 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::Client
+ class Key < Base
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ attr_reader :state
+ attr_reader :username
+ attr_reader :password
+ attr_reader :public_key
+ attr_reader :fingerprint
+
+ # Key model methods
+ def pem
+ @public_key
+ end
+
+ def destroy!
+ destroy_key(_id)
+ end
+
+ # Parse the Key entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the key
+ #
+ def self.parse(xml_body)
+ {
+ :state => xml_body.text_at(:state),
+ :username => xml_body.text_at(:username),
+ :password => xml_body.text_at(:password),
+ :fingerprint => xml_body.text_at(:fingerprint),
+ :public_key => xml_body.text_at(:pem)
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/realm.rb b/client/lib/deltacloud/client/models/realm.rb
new file mode 100644
index 0000000..d01739c
--- /dev/null
+++ b/client/lib/deltacloud/client/models/realm.rb
@@ -0,0 +1,29 @@
+# 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::Client
+ class Realm < Base
+
+ attr_reader :limit
+ attr_reader :state
+
+ def self.parse(xml_body)
+ {
+ :state => xml_body.text_at('state'),
+ :limit => xml_body.text_at('limit')
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/storage_snapshot.rb b/client/lib/deltacloud/client/models/storage_snapshot.rb
new file mode 100644
index 0000000..f844e9f
--- /dev/null
+++ b/client/lib/deltacloud/client/models/storage_snapshot.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::Client
+ class StorageSnapshot < Base
+
+ include Deltacloud::Client::Methods::StorageSnapshot
+ include Deltacloud::Client::Methods::StorageVolume
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ attr_reader :created
+ attr_reader :storage_volume_id
+
+ # StorageSnapshot model methods
+ #
+ def storage_volume
+ super(storage_volume_id)
+ end
+
+ # Syntax sugar for destroying the current instance
+ # of StorageSnapshot
+ #
+ def destroy!
+ destroy_storage_snapshot(_id)
+ end
+
+
+ # Parse the StorageSnapshot entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the storage_snapshot
+ #
+ def self.parse(xml_body)
+ {
+ :created => xml_body.text_at(:created),
+ :storage_volume_id => xml_body.attr_at('storage_volume', :id)
+ }
+ end
+ end
+end
diff --git a/client/lib/deltacloud/client/models/storage_volume.rb b/client/lib/deltacloud/client/models/storage_volume.rb
new file mode 100644
index 0000000..26e1e80
--- /dev/null
+++ b/client/lib/deltacloud/client/models/storage_volume.rb
@@ -0,0 +1,96 @@
+# 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::Client
+ class StorageVolume < Base
+
+ include Deltacloud::Client::Methods::Common
+ include Deltacloud::Client::Methods::Instance
+ include Deltacloud::Client::Methods::StorageVolume
+ include Deltacloud::Client::Methods::StorageSnapshot
+
+ attr_accessor :created
+ attr_accessor :state
+ attr_accessor :capacity
+ attr_accessor :capacity_unit
+ attr_accessor :device
+ attr_accessor :realm_id
+ attr_accessor :kind
+ attr_accessor :mount
+
+ # Check if the current volume is attached to an Instance
+ #
+ def attached?
+ !mount[:instance].nil?
+ end
+
+ # Attach this volume to the instance
+ #
+ def attach(instance_id, device=nil)
+ attach_storage_volume(_id, instance_id, device) && reload!
+ end
+
+ # Detach this volume from the currently attached instance
+ #
+ def detach!
+ detach_storage_volume(_id) && reload!
+ end
+
+ # Destroy the storage volume
+ #
+ def destroy!
+ destroy_storage_volume(_id)
+ end
+
+ # Syntax sugar for creating a snapshot from volume
+ # See: +create_storage_snapshot+
+ #
+ def snapshot!(snapshot_opts={})
+ snap = create_storage_snapshot(_id, snapshot_opts)
+ reload! && snap
+ end
+
+ def instance
+ super(mount[:instance])
+ end
+
+ def self.parse(xml_body)
+ {
+ :created => xml_body.text_at('created'),
+ :state => xml_body.text_at('state'),
+ :device => xml_body.text_at('device'),
+ :capacity => xml_body.text_at('capacity'),
+ :capacity_unit => xml_body.attr_at('capacity', :unit),
+ :state => xml_body.text_at('state'),
+ :realm_id => xml_body.attr_at('realm', :id),
+ :kind => xml_body.text_at('kind'),
+ :mount => {
+ :instance => xml_body.attr_at('mount/instance', :id),
+ :device => xml_body.attr_at('mount/device', :name)
+ }
+ }
+ end
+
+ private
+
+ def reload!
+ new_volume = storage_volume(_id)
+ update_instance_variables!(
+ :state => new_volume.state,
+ :mount => new_volume.mount
+ )
+ end
+ end
+end
diff --git a/client/lib/deltacloud/core_ext.rb b/client/lib/deltacloud/core_ext.rb
new file mode 100644
index 0000000..e85cdba
--- /dev/null
+++ b/client/lib/deltacloud/core_ext.rb
@@ -0,0 +1,19 @@
+# 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_relative './core_ext/element'
+require_relative './core_ext/string'
+require_relative './core_ext/fixnum'
+require_relative './core_ext/string'
diff --git a/client/lib/deltacloud/core_ext/element.rb b/client/lib/deltacloud/core_ext/element.rb
new file mode 100644
index 0000000..43b3c61
--- /dev/null
+++ b/client/lib/deltacloud/core_ext/element.rb
@@ -0,0 +1,32 @@
+# 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 Nokogiri
+ module XML
+
+ class Element
+
+ def text_at(xpath)
+ (val = self.at(xpath)) ? val.text.strip : nil
+ end
+
+ def attr_at(xpath, attr_name)
+ (val = self.at(xpath)) ? val[attr_name.to_s.strip] : nil
+ end
+
+ end
+
+ end
+end
diff --git a/client/lib/deltacloud/core_ext/fixnum.rb b/client/lib/deltacloud/core_ext/fixnum.rb
new file mode 100644
index 0000000..5eb6e1b
--- /dev/null
+++ b/client/lib/deltacloud/core_ext/fixnum.rb
@@ -0,0 +1,30 @@
+# 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.
+
+class Fixnum
+
+ def is_redirect?
+ (self.to_s =~ /^3(\d+)$/) ? true : false
+ end
+
+ def is_ok?
+ (self.to_s =~ /^2(\d+)$/) ? true : false
+ end
+
+ def is_no_content?
+ self == 204
+ end
+
+end
diff --git a/client/lib/deltacloud/core_ext/nil.rb b/client/lib/deltacloud/core_ext/nil.rb
new file mode 100644
index 0000000..6612326
--- /dev/null
+++ b/client/lib/deltacloud/core_ext/nil.rb
@@ -0,0 +1,22 @@
+# 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.
+
+class NilClass
+
+ def to_xml
+ raise Deltacloud::Client::InvalidXMLError.new('Server returned empty body')
+ end
+
+end
diff --git a/client/lib/deltacloud/core_ext/string.rb b/client/lib/deltacloud/core_ext/string.rb
new file mode 100644
index 0000000..e02aacf
--- /dev/null
+++ b/client/lib/deltacloud/core_ext/string.rb
@@ -0,0 +1,49 @@
+# 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.
+
+class String
+
+ # Used to automagically convert any XML in String
+ # (like HTTP response body) to Nokogiri::XML object
+ #
+ # If Nokogiri::XML fails, InvalidXMLError is returned.
+ #
+ def to_xml
+ Nokogiri::XML(self)
+ end
+
+ unless method_defined? :camelize
+ def camelize
+ split('_').map { |w| w.capitalize }.join
+ end
+ end
+
+ unless method_defined? :pluralize
+ def pluralize
+ return self + 'es' if self =~ /ess$/
+ return self[0, self.length-1] + "ies" if self =~ /ty$/
+ return self if self =~ /data$/
+ self + "s"
+ end
+ end
+
+ unless method_defined? :singularize
+ def singularize
+ return self.gsub(/ies$/, 'y') if self =~ /ies$/
+ return self.gsub(/es$/, '') if self =~ /sses$/
+ self.gsub(/s$/, '')
+ end
+ end
+end
diff --git a/client/lib/deltacloud/error_response.rb b/client/lib/deltacloud/error_response.rb
new file mode 100644
index 0000000..428b5ec
--- /dev/null
+++ b/client/lib/deltacloud/error_response.rb
@@ -0,0 +1,92 @@
+# 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 ErrorResponse < Faraday::Response::Middleware
+
+ include Deltacloud::Client::Helpers::Model
+
+ # This method tries to parse the error XML from Deltacloud API
+ # In case there is no error returned in body, it will try to use
+ # the generic error reporting.
+ #
+ # - klass -> Deltacloud::Client::+Class+
+ # - message -> Exception message (overiden by error body message if
+ # present)
+ # - error -> Deltacloud XML error representation
+ #
+ def client_error(name, error, message=nil)
+ args = {
+ :message => message,
+ :status => error ? error[:status] : '500'
+ }
+ # If Deltacloud API send error in response body, parse it.
+ # Otherwise, when DC API send just plain text error, use
+ # it as exception message.
+ # If DC API does not send anything back, then fallback to
+ # the 'message' attribute.
+ #
+ if error and !error[:body].empty?
+ if xml_error?(error)
+ args.merge! parse_error(error[:body].to_xml.root)
+ else
+ args[:message] = error[:body]
+ end
+ end
+ error(name).new(args)
+ end
+
+ def call(env)
+ @app.call(env).on_complete do |e|
+ case e[:status].to_s
+ when '401'
+ raise client_error(:authentication_error, e, 'Invalid :api_user or :api_password')
+ when '405'
+ raise client_error(
+ :invalid_state, e, 'Resource state does not permit this action'
+ )
+ when '404'
+ raise client_error(:not_found, e, 'Object not found')
+ when /40\d/
+ raise client_error(:client_failure, e)
+ when '500'
+ raise client_error(:server_error, e)
+ when '502'
+ raise client_error(:backend_error, e)
+ when '501'
+ raise client_error(:not_supported, e)
+ end
+ end
+ end
+
+ private
+
+ def xml_error?(error)
+ error[:body].to_xml.root && error[:body].to_xml.root.name == 'error'
+ end
+
+ # Parse the Deltacloud API error body to Hash
+ #
+ def parse_error(body)
+ args = {}
+ args[:original_error] = body.to_s
+ args[:server_backtrace] = body.text_at('backtrace')
+ args[:message] ||= body.text_at('message')
+ args[:driver] = body.attr_at('backend', 'driver')
+ args[:provider] = body.attr_at('backend', 'provider')
+ args
+ end
+ end
+end
diff --git a/client/lib/documentation.rb b/client/lib/documentation.rb
deleted file mode 100644
index 88f0861..0000000
--- a/client/lib/documentation.rb
+++ /dev/null
@@ -1,59 +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 Documentation
-
- attr_reader :api, :description, :params, :collection_operations
- attr_reader :collection, :operation
-
- def initialize(api, opts={})
- @description, @api = opts[:description], api
- @params = parse_parameters(opts[:params]) if opts[:params]
- @collection_operations = opts[:operations] if opts[:operations]
- @collection = opts[:collection]
- @operation = opts[:operation]
- self
- end
-
- def operations
- @collection_operations.collect { |o| api.documentation(@collection, o) }
- end
-
- class OperationParameter
- attr_reader :name
- attr_reader :type
- attr_reader :required
- attr_reader :description
-
- def initialize(data)
- @name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
- end
-
- def to_comment
- " # @param [#{@type}, #{@name}] #{@description}"
- end
-
- end
-
- private
-
- def parse_parameters(params)
- params.collect { |p| OperationParameter.new(p) }
- end
-
- end
-
-end
diff --git a/client/lib/errors.rb b/client/lib/errors.rb
deleted file mode 100644
index d0eff44..0000000
--- a/client/lib/errors.rb
+++ /dev/null
@@ -1,140 +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 HTTPError
-
- class ClientError < StandardError
-
- attr_reader :params, :driver, :provider
-
- def initialize(code, message, opts={}, backtrace=nil)
- @params, @driver, @provider = opts[:params], opts[:driver], opts[:provider]
- if code.to_s =~ /^5(\d{2})/
- message += "\nParameters: #{@params.inspect}\n"
- message += "Driver: #{@driver}@#{@provider}"
- end
- super("#{code}\n\n#{self.class.superclass}: #{message}\n\n")
- # If server provided us the backtrace, then replace client backtrace
- # with the server one.
- set_backtrace(backtrace) unless backtrace.nil?
- end
- end
-
- class ServerError < ClientError; end
- class UknownError < ClientError; end
-
- # For sake of consistent documentation we need to create
- # this exceptions manually, instead of using some meta-programming.
- # Client will really appreciate this it will try to catch some
- # specific exception.
-
- # Client errors (4xx)
- class BadRequest < ClientError; end
- class Unauthorized < ClientError; end
- class Forbidden < ClientError; end
- class NotFound < ClientError; end
- class MethodNotAllowed < ClientError; end
- class NotAcceptable < ClientError; end
- class RequestTimeout < ClientError; end
- class Gone < ClientError; end
- class ExpectationFailed < ClientError; end
- class UnsupportedMediaType < ClientError; end
-
- # Server errors (5xx)
- class DeltacloudError < ServerError; end
- class ProviderError < ServerError; end
- class ProviderTimeout < ServerError; end
- class ServiceUnavailable < ServerError; end
- class NotImplemented < ServerError; end
-
- class ExceptionHandler
-
- attr_reader :http_status_code, :message, :trace
-
- def initialize(status_code, message=nil, opts={}, backtrace=nil, &block)
- @http_status_code = status_code.to_i
- @trace = backtrace
- @message = message || client_error_messages[status_code] || 'No error message received'
- @options = opts
- instance_eval(&block) if block_given?
- end
-
- def on(code, exception_class)
- if code == @http_status_code
- raise exception_class.new(code, @message, @options, @trace)
- end
- end
-
- private
-
- def client_error_messages
- {
- 400 => 'The request could not be understood by the server due to malformed syntax.',
- 401 => 'Authentication required for this request or invalid credentials provided.',
- 403 => 'Requested operation is not allowed for this resource.',
- 404 => 'Not Found',
- 405 => 'Method not allowed for this resource.',
- 406 => 'Requested media type is not supported by server.',
- 408 => 'The client did not produce a request within the time that the server was prepared to wait.',
- 410 => 'The resource is no longer available'
- }
- end
-
- end
-
- def self.parse_response_error(response)
-
- end
-
- def self.client_error(code)
- ExceptionHandler.new(code) do
- # Client errors
- on 400, BadRequest
- on 401, Unauthorized
- on 403, Forbidden
- on 404, NotFound
- on 405, MethodNotAllowed
- on 406, NotAcceptable
- on 408, RequestTimeout
- on 410, Gone
- end
- end
-
- def self.server_error(code, message, opts={}, backtrace=nil)
- ExceptionHandler.new(code, message, opts, backtrace) do
- # Client errors
- on 400, BadRequest
- on 401, Unauthorized
- on 403, Forbidden
- on 404, NotFound
- on 405, MethodNotAllowed
- on 406, NotAcceptable
- on 408, RequestTimeout
- on 410, Gone
- on 415, UnsupportedMediaType
- on 417, ExpectationFailed
- # Server errors
- on 500, DeltacloudError
- on 501, NotImplemented
- on 502, ProviderError
- on 503, ServiceUnavailable
- on 504, ProviderTimeout
- end
- raise Deltacloud::HTTPError::UnknownError.new(code, message, opts, backtrace)
- end
-
- end
-end
diff --git a/client/lib/hwp_properties.rb b/client/lib/hwp_properties.rb
deleted file mode 100644
index 686deaa..0000000
--- a/client/lib/hwp_properties.rb
+++ /dev/null
@@ -1,61 +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 HWP
-
- class Property
- attr_reader :name, :unit, :value, :kind
-
- def initialize(xml, name)
- @name, @kind, @value, @unit = xml['name'], xml['kind'].to_sym, xml['value'], xml['unit']
- declare_ranges(xml)
- self
- end
-
- def present?
- ! @value.nil?
- end
-
- private
-
- def declare_ranges(xml)
- case xml['kind']
- when 'range' then
- self.class.instance_eval do
- attr_reader :range
- end
- @range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
- when 'enum' then
- self.class.instance_eval do
- attr_reader :options
- end
- @options = xml.xpath('enum/entry').collect { |e| e['value'] }
- end
- end
-
- end
-
- # FloatProperty is like Property but return value is Float instead of String.
- class FloatProperty < Property
- def initialize(xml, name)
- super(xml, name)
- @value = @value.to_f if @value
- end
- end
- end
-
-end
diff --git a/client/lib/instance_state.rb b/client/lib/instance_state.rb
deleted file mode 100644
index 6a64f57..0000000
--- a/client/lib/instance_state.rb
+++ /dev/null
@@ -1,44 +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 InstanceState
-
- class State
- attr_reader :name
- attr_reader :transitions
-
- def initialize(name)
- @name, @transitions = name, []
- end
- end
-
- class Transition
- attr_reader :to
- attr_reader :action
-
- def initialize(to, action)
- @to = to
- @action = action
- end
-
- def auto?
- @action.nil?
- end
- end
-
- end
-
-end
diff --git a/client/lib/string.rb b/client/lib/string.rb
deleted file mode 100644
index fb90564..0000000
--- a/client/lib/string.rb
+++ /dev/null
@@ -1,59 +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.
-
-class String
-
- unless method_defined?(:classify)
- # Create a class name from string
- def classify
- self.singularize.camelize
- end
- end
-
- unless method_defined?(:camelize)
- # Camelize converts strings to UpperCamelCase
- def camelize
- self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
- end
- end
-
- unless method_defined?(:singularize)
- # Strip 's' character from end of string
- def singularize
- self.gsub(/s$/, '')
- end
- end
-
- unless method_defined?(:pluralize)
- def pluralize
- return self + 'es' if self =~ /ess$/
- return self[0, self.length-1] + "ies" if self =~ /ty$/
- return self if self =~ /data$/
- self + "s"
- end
- end
-
- # Convert string to float if string value seems like Float
- def convert
- return self.to_f if self.strip =~ /^([\d\.]+$)/
- self
- end
-
- # Simply converts whitespaces and - symbols to '_' which is safe for Ruby
- def sanitize
- self.strip.gsub(/(\W+)/, '_')
- end
-
-end
diff --git a/client/support/method_test_template.erb b/client/support/method_test_template.erb
new file mode 100644
index 0000000..89831fb
--- /dev/null
+++ b/client/support/method_test_template.erb
@@ -0,0 +1,53 @@
+# 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_relative '../test_helper'
+
+describe Deltacloud::Client::Methods::<%=name.to_s.camelize%> do
+
+ before do
+ VCR.insert_cassette(__name__)
+ @client = new_client
+ end
+
+ after do
+ VCR.eject_cassette
+ end
+
+ it 'supports #<%=name.to_s.pluralize%>' do
+ @client.must_respond_to :<%=name.to_s.pluralize%>
+ @client.<%=name.to_s.pluralize%>.must_be_kind_of Array
+ @client.<%=name.to_s.pluralize%>.each { |r| r.must_be_instance_of Deltacloud::Client::<%=name.to_s.camelize%> }
+ end
+
+ it 'supports filtering #<%=name.to_s.pluralize%> by :id param' do
+ result = @client.<%=name.to_s.pluralize%>(:id => 'XXX')
+ result.must_be_kind_of Array
+ result.size.must_equal 1
+ result.first.must_be_instance_of Deltacloud::Client::<%=name.to_s.camelize%>
+ result = @client.<%=name.to_s.pluralize%>(:id => 'unknown')
+ result.must_be_kind_of Array
+ result.size.must_equal 0
+ end
+
+ it 'support #<%=name.to_s%>' do
+ @client.must_respond_to :<%=name%>
+ result = @client.<%=name%>('XXX')
+ result.must_be_instance_of Deltacloud::Client::<%=name.to_s.camelize%>
+ lambda { @client.<%=name%>(nil) }.must_raise Deltacloud::Client::NotFound
+ lambda { @client.<%=name%>('foo') }.must_raise Deltacloud::Client::NotFound
+ end
+
+end
diff --git a/client/support/methods_template.erb b/client/support/methods_template.erb
new file mode 100644
index 0000000..b64b1eb
--- /dev/null
+++ b/client/support/methods_template.erb
@@ -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::Client
+ module Methods
+ module <%=name.to_s.camelize%>
+
+ # Retrieve list of all <%=name%> entities
+ #
+ # Filter options:
+ #
+ # - :id -> Filter entities using 'id' attribute
+ #
+ def <%=name.to_s.pluralize%>(filter_opts={})
+ from_collection :<%=name.to_s.pluralize%>,
+ connection.get(api_uri('<%=name.to_s.pluralize%>'), filter_opts)
+ end
+
+ # Retrieve the single <%=name%> entity
+ #
+ # - <%=name%>_id -> <%=name.to_s.camelize%> entity to retrieve
+ #
+ def <%=name%>(<%=name%>_id)
+ from_resource :<%=name%>,
+ connection.get(api_uri("<%=name.to_s.pluralize%>/#{<%=name%>_id}"))
+ end
+
+ # Create a new <%=name%>
+ #
+ # - create_opts
+ #
+ # def create_<%=name%>(create_opts={})
+ # must_support! :<%=name.to_s.pluralize%>
+ # response = connection.post(api_uri('<%=name.to_s.pluralize%>')) do |request|
+ # request.params = create_opts
+ # end
+ # model(:<%=name%>).convert(self, response.body)
+ # end
+
+ end
+ end
+end
diff --git a/client/support/model_template.erb b/client/support/model_template.erb
new file mode 100644
index 0000000..0def783
--- /dev/null
+++ b/client/support/model_template.erb
@@ -0,0 +1,45 @@
+# 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::Client
+ class <%=name.to_s.camelize%> < Base
+
+ include Deltacloud::Client::Methods::<%=name.to_s.camelize%>
+
+ # Inherited attributes: :_id, :name, :description
+
+ # Custom attributes:
+ #
+ # attr_reader :state
+ # attr_reader :realm_id
+
+ # <%=name.to_s.camelize%> model methods
+ #
+ # def reboot!
+ # <%=name%>_reboot(_id)
+ # end
+
+ # Parse the <%=name.to_s.camelize %> entity from XML body
+ #
+ # - xml_body -> Deltacloud API XML representation of the <%=name%>
+ #
+ def self.parse(xml_body)
+ {
+ # :state => xml_body.text_at(:state),
+ # :realm_id => xml_body.attr_at('realm', :id)
+ }
+ end
+ end
+end
--
1.8.1.4