You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by ma...@redhat.com on 2012/07/20 18:55:47 UTC
[PATCH 1/2] Adds initial Deltacloud API tests
From: marios <ma...@redhat.com>
Signed-off-by: marios <ma...@redhat.com>
---
tests/Rakefile | 28 ++---
tests/deltacloud/base_api_test.rb | 111 ++++++++++++++++
tests/deltacloud/buckets_test.rb | 187 ++++++++++++++++++++++++++++
tests/deltacloud/config.yaml | 18 +++
tests/deltacloud/test_setup.rb | 119 ++++++++++++++++++
5 files changed, 443 insertions(+), 20 deletions(-)
create mode 100644 tests/deltacloud/base_api_test.rb
create mode 100644 tests/deltacloud/buckets_test.rb
create mode 100644 tests/deltacloud/config.yaml
create mode 100644 tests/deltacloud/hardware_profiles_test.rb
create mode 100644 tests/deltacloud/images_test.rb
create mode 100644 tests/deltacloud/instance_states_test.rb
create mode 100644 tests/deltacloud/instances_test.rb
create mode 100644 tests/deltacloud/keys_test.rb
create mode 100644 tests/deltacloud/realms_test.rb
create mode 100644 tests/deltacloud/storage_snapshots_test.rb
create mode 100644 tests/deltacloud/storage_volumes_test.rb
create mode 100644 tests/deltacloud/test_setup.rb
diff --git a/tests/Rakefile b/tests/Rakefile
index dfdb209..0475ae5 100644
--- a/tests/Rakefile
+++ b/tests/Rakefile
@@ -19,28 +19,16 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
-require 'rake'
-require 'cucumber'
-require 'cucumber/rake/task'
+require 'rake/testtask'
+require 'deltacloud/test_setup.rb'
-DRIVER=ENV['API_DRIVER'] || 'mock'
+namespace :test do
-Cucumber::Rake::Task.new(:features) do |t|
- t.cucumber_opts = "../tests/#{DRIVER} --format html --out ../tests/tmp/cucumber_#{DRIVER}.html"
- t.rcov = false
-end
-
-Cucumber::Rake::Task.new(:cucumber) do |t|
- t.cucumber_opts = "../tests/#{DRIVER} --format pretty"
- t.rcov = false
-end
+ file_list = Rake::FileList.new(deltacloud_test_file_names)
+ Rake::TestTask.new(:deltacloud) do |t|
+ t.test_files = file_list << "deltacloud/base_api_test.rb"
+ t.options = "-v"
+ end
-Cucumber::Rake::Task.new(:rcov) do |t|
- t.cucumber_opts = "../tests/#{DRIVER} --format pretty"
- t.rcov = true
- t.rcov_opts << %[-o "../tests/tmp/coverage_#{DRIVER}"]
end
-Cucumber::Rake::Task.new(:junit) do |t|
- t.cucumber_opts = "../tests/#{DRIVER} --format junit --out #{File.join(File.dirname(__FILE__), "tmp", "junit_#{DRIVER}")}"
-end
diff --git a/tests/deltacloud/base_api_test.rb b/tests/deltacloud/base_api_test.rb
new file mode 100644
index 0000000..2035b8f
--- /dev/null
+++ b/tests/deltacloud/base_api_test.rb
@@ -0,0 +1,111 @@
+$:.unshift File.join(File.dirname(__FILE__), '..')
+require "deltacloud/test_setup.rb"
+
+describe "Deltacloud API Entry Point" do
+
+ it 'return status 200 OK when accessing API entrypoint' do
+ res = get
+ res.code.must_equal 200
+ end
+
+ it 'advertise the current driver in API entrypoint' do
+ res = get({:accept => :xml})
+ driver = xml_response(res).root[:driver]
+ driver.wont_be_nil
+ DRIVERS.include?(driver).must_equal true
+ end
+
+ it 'advertise the current API version in API entrypoint' do
+ res = get({:accept => :xml})
+ version = xml_response(res).root[:version]
+ version.wont_be_nil
+ version.must_equal API_VERSION
+ end
+
+ it 'advertise the current API version in HTTP headers' do
+ res = get
+ res.headers[:server].must_equal "Apache-Deltacloud/#{API_VERSION}"
+ end
+
+ it 'must include the ETag in HTTP headers' do
+ res = get
+ res.headers[:etag].wont_be_nil
+ end
+
+ it 'advertise collections in API entrypoint' do
+ res = get({:accept => :xml})
+ xml_response(res).xpath('//api/link').wont_be_empty
+ end
+
+ it 'include the :href and :rel attribute for each collection in API entrypoint' do
+ xml_response(get({:accept => :xml})).xpath("//api/link").each do |collection|
+ collection[:href].wont_be_nil
+ collection[:rel].wont_be_nil
+ end
+ end
+
+ it 'uses the absolute URI in the :href attribute for each collection in API entrypoint' do
+ xml_response(get({:accept => :xml})).xpath("//api/link").each do |collection|
+ collection[:href].must_match /^http/
+ end
+ end
+
+ it 'advertise features for some collections in API entrypoint' do
+ xml_doc = xml_response(get({:accept => :xml}))
+ xml_doc.xpath("//api/link/feature").wont_be_empty
+ end
+
+ it 'advertise the name of the feature for some collections in API entrypoint' do
+ xml_response(get({:accept => :xml})).xpath("//api/link/feature").each do |feature|
+ feature[:name].wont_be_nil
+ end
+ end
+
+ it 'must change the media type from XML to JSON using Accept headers' do
+ res = get({:accept => :json})
+ res.headers[:content_type].must_equal 'application/json'
+ end
+
+ it 'must change the media type to JSON using the "?format" parameter in URL' do
+ res = get({}, "?format=json")
+ res.headers[:content_type].must_equal 'application/json'
+ end
+
+ it 'must change the driver when using X-Deltacloud-Driver HTTP header' do
+ res = xml_response(get({'X-Deltacloud-Driver'=> 'ec2', :accept=> :xml}))
+ res.root[:driver].must_equal 'ec2'
+ res = xml_response(get({'X-Deltacloud-Driver'=> 'mock', :accept=> :xml}))
+ res.root[:driver].must_equal 'mock'
+ end
+
+ it 'must change the features when driver is swapped using HTTP headers' do
+ res = xml_response(get({'X-Deltacloud-Driver'=> 'ec2', :accept=> :xml}))
+ # The 'user_name' feature is not supported currently for the EC2 driver
+ (res/'api/link/feature').map { |f| f[:name] }.wont_include 'user_name'
+ res = xml_response(get({'X-Deltacloud-Driver'=> 'mock', :accept=> :xml}))
+ # But it's supported in Mock driver
+ (res/'api/link/feature').map { |f| f[:name] }.must_include 'user_name'
+ end
+
+ it 'must re-validate the driver credentials when using "?force_auth" parameter in URL' do
+ proc {get({ :params => {:force_auth => '1'} })}.must_raise RestClient::Request::Unauthorized
+ res = get({ "X-Deltacloud-Driver"=>"mock", :params=>{:force_auth => '1'}, :Authorization=>"Basic #{Base64.encode64('mockuser:mockpassword')}" })
+ res.code.must_equal 200
+ end
+
+ it 'must change the API PROVIDER using the /api;provider matrix parameter in URI' do
+ res = xml_response(get({}, ';provider=test1'))
+ res.root[:provider].wont_be_nil
+ res.root[:provider].must_equal 'test1'
+ res = xml_response(get({}, ';provider=test2'))
+ res.root[:provider].must_equal 'test2'
+ end
+
+ it 'must change the API DRIVER using the /api;driver matrix parameter in URI' do
+ res = xml_response(get({}, ';driver=ec2'))
+ res.root[:driver].must_equal 'ec2'
+ res = xml_response(get({}, ';driver=mock'))
+ res.root[:driver].must_equal 'mock'
+ end
+
+end
diff --git a/tests/deltacloud/buckets_test.rb b/tests/deltacloud/buckets_test.rb
new file mode 100644
index 0000000..3a525ed
--- /dev/null
+++ b/tests/deltacloud/buckets_test.rb
@@ -0,0 +1,187 @@
+$:.unshift File.join(File.dirname(__FILE__), '..')
+require "deltacloud/test_setup.rb"
+
+
+require 'ruby-debug'
+BUCKETS = "/buckets"
+
+#make sure we have at least one bucket and blob to test
+bucket, blob = create_a_bucket_and_blob
+
+features_hash = discover_features
+
+describe 'Deltacloud API buckets collection' do
+
+MiniTest::Unit.after_tests{
+ #finally delete the bucket/blob we created for the tests:
+ delete_bucket_and_blob(bucket, blob)
+}
+
+ it 'must advertise the buckets collection in API entrypoint' do
+ res = xml_response(get)
+ (res/'api/link[@rel=buckets]').wont_be_empty
+ end
+
+ it 'must require authentication to access the "bucket" collection' do
+ proc { get({},BUCKETS) }.must_raise RestClient::Request::Unauthorized
+ end
+
+ it 'should respond with HTTP_OK when accessing the :buckets collection with authentication' do
+ res = get({}, BUCKETS, true)
+ res.code.must_equal 200
+ end
+
+ it 'should be possible to create bucket with POST /api/buckets and delete it with DELETE /api/buckets/:id' do
+ bucket_name = random_name
+ #create bucket
+ res = post({:name=>bucket_name}, BUCKETS, {}, true)
+ #check response
+ res.code.must_equal 201
+ xml_res = xml_response(res)
+ xml_res.xpath("//bucket/name").text.must_equal bucket_name
+ xml_res.xpath("//bucket").size.must_equal 1
+ xml_res.xpath("//bucket")[0][:id].must_equal bucket_name
+ #GET bucket
+ res = get({}, BUCKETS+"/"+bucket_name, true)
+ res.code.must_equal 200
+ #DELETE bucket
+ res = delete({}, BUCKETS+"/"+bucket_name)
+ res.code.must_equal 204
+ end
+
+ it 'should be possible to specify location for POST /api/buckets if bucket_location feature' do
+ skip("No bucket_location feature specified for driver #{API_DRIVER} running at #{API_URL}... skipping test") unless features_hash["buckets"].include?("bucket_location")
+ bucket_name = random_name
+# res = post({:name=>bucket_name, :bucket_location=>
+ end
+
+
+ it 'should support the JSON media type' do
+ res = get({:accept=>:json}, BUCKETS, true)
+ res.code.must_equal 200
+ res.headers[:content_type].must_equal 'application/json'
+ assert_silent {JSON.parse(res)}
+ end
+
+ it 'must include the ETag in HTTP headers' do
+ res = get({}, BUCKETS, true)
+ res.headers[:etag].wont_be_nil
+ end
+
+ it 'must have the "buckets" element on top level' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ xml_res.root.name.must_equal 'buckets'
+ end
+
+ it 'must have some "bucket" elements inside "buckets"' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').wont_be_empty
+ end
+
+ it 'must provide the :id attribute for each bucket in collection' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ r[:id].wont_be_nil
+ end
+ end
+
+ it 'must include the :href attribute for each "bucket" element in collection' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ r[:href].wont_be_nil
+ end
+ end
+
+ it 'must use the absolute URL in each :href attribute' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ r[:href].must_match /^http/
+ end
+ end
+
+ it 'must have the URL ending with the :id of the bucket' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ r[:href].must_match /#{r[:id]}$/
+ end
+ end
+
+ it 'must have the "name" element defined for each bucket in collection' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ (r/'name').wont_be_nil
+ (r/'name').wont_be_empty
+ end
+ end
+
+ it 'must have the "size" element defined for each bucket in collection' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ (r/'size').wont_be_nil
+ (r/'size').wont_be_empty
+ end
+ end
+
+ it 'must return 200 OK when following the URL in bucket element' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ bucket_res = RestClient.get(r[:href], {:Authorization=>BASIC_AUTH})
+ bucket_res.code.must_equal 200
+ end
+ end
+
+ it 'must have the "name" element for the bucket and it should match with the one in collection' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ bucket_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}", true))
+ (bucket_xml/'name').wont_be_empty
+ (bucket_xml/'name').first.text.must_equal((r/'name').first.text)
+ end
+ end
+
+ it 'all "blob" elements for the bucket should match the ones in collection' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ bucket_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}", true))
+ (bucket_xml/'bucket/blob').each do |b|
+ b[:id].wont_be_nil
+ b[:href].wont_be_nil
+ b[:href].must_match /^http/
+ b[:href].must_match /#{r[:id]}\/#{b[:id]}$/
+ end
+ end
+ end
+
+ it 'must allow to get all blobs details and the details should be set correctly' do
+ xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true))
+ (xml_res/'buckets/bucket').each do |r|
+ bucket_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}", true))
+ (bucket_xml/'bucket/blob').each do |b|
+ blob_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}/#{b[:id]}", true))
+ blob_xml.root.name.must_equal 'blob'
+ blob_xml.root[:id].must_equal b[:id]
+ (blob_xml/'bucket').wont_be_empty
+ (blob_xml/'bucket').size.must_equal 1
+ (blob_xml/'bucket').first.text.wont_be_nil
+ (blob_xml/'bucket').first.text.must_equal r[:id]
+ (blob_xml/'content_length').wont_be_empty
+ (blob_xml/'content_length').size.must_equal 1
+ (blob_xml/'content_length').first.text.must_match /^(\d+)$/
+ (blob_xml/'content_type').wont_be_empty
+ (blob_xml/'content_type').size.must_equal 1
+ (blob_xml/'content_type').first.text.wont_be_nil
+ (blob_xml/'last_modified').wont_be_empty
+ (blob_xml/'last_modified').size.must_equal 1
+ (blob_xml/'last_modified').first.text.wont_be_empty
+ (blob_xml/'content').wont_be_empty
+ (blob_xml/'content').size.must_equal 1
+ (blob_xml/'content').first[:rel].wont_be_nil
+ (blob_xml/'content').first[:rel].must_equal 'blob_content'
+ (blob_xml/'content').first[:href].wont_be_nil
+ (blob_xml/'content').first[:href].must_match /^http/
+ (blob_xml/'content').first[:href].must_match /\/content$/
+ end
+ end
+ end
+
+end
diff --git a/tests/deltacloud/config.yaml b/tests/deltacloud/config.yaml
new file mode 100644
index 0000000..e94a29e
--- /dev/null
+++ b/tests/deltacloud/config.yaml
@@ -0,0 +1,18 @@
+---
+api_url: "http://localhost:3001/api"
+#MOCK DRIVER CONFIG:
+mock:
+ user: "mockuser"
+ password: "mockpassword"
+#EC2 DRIVER CONFIG:
+ec2:
+ user: #your EC2 KEY HERE
+ password: #your EC2 SECRET_KEY HERE
+ bucket_locations:
+ #location constraint : provider
+ "EU" : "eu-west-1"
+ "sa-east-1" : "sa-east-1"
+ "us-west-1" : "us-west-1"
+ "us-west-2" : "us-west-2"
+ "ap-southeast-1" : "ap-southeast-1"
+ "ap-northeast-1" : "ap-northeast-1"
diff --git a/tests/deltacloud/hardware_profiles_test.rb b/tests/deltacloud/hardware_profiles_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/images_test.rb b/tests/deltacloud/images_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/instance_states_test.rb b/tests/deltacloud/instance_states_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/instances_test.rb b/tests/deltacloud/instances_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/keys_test.rb b/tests/deltacloud/keys_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/realms_test.rb b/tests/deltacloud/realms_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/storage_snapshots_test.rb b/tests/deltacloud/storage_snapshots_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/storage_volumes_test.rb b/tests/deltacloud/storage_volumes_test.rb
new file mode 100644
index 0000000..e69de29
diff --git a/tests/deltacloud/test_setup.rb b/tests/deltacloud/test_setup.rb
new file mode 100644
index 0000000..0c7fe6b
--- /dev/null
+++ b/tests/deltacloud/test_setup.rb
@@ -0,0 +1,119 @@
+require 'rubygems'
+require 'minitest/autorun'
+require 'rest_client'
+require 'nokogiri'
+require 'json'
+require 'base64'
+require 'yaml'
+
+#SETUP
+$:.unshift File.join(File.dirname(__FILE__), '..')
+CONFIG = YAML.load(File.open("deltacloud/config.yaml"))
+API_URL = CONFIG["api_url"]
+API_DRIVER = RestClient.get(API_URL) do |response, request, result|
+ Nokogiri::XML(response).root[:driver]
+end
+raise Exception.new("Can't find config for driver: #{API_DRIVER} currently running at #{API_URL} in config.yaml file") unless CONFIG[API_DRIVER] and CONFIG[API_DRIVER]["user"] and CONFIG[API_DRIVER]["password"]
+USER = CONFIG[API_DRIVER]["user"]
+PASSWORD= CONFIG[API_DRIVER]["password"]
+BASIC_AUTH="Basic #{Base64.encode64(USER+":"+PASSWORD)}"
+
+DRIVERS = RestClient.get(API_URL+"/drivers") do |response, request, result|
+ Nokogiri::XML(response).xpath("//driver/name").inject([]){|res, c| res << c.text.downcase; res}
+end
+API_VERSION = Nokogiri::XML(RestClient.get API_URL).root[:version]
+#SETUP
+
+def xml_response(xml)
+ Nokogiri::XML(xml)
+end
+
+def json_response(json)
+ JSON.parse(json)
+end
+
+def get(params={}, path="", authenticate = false)
+ if authenticate
+ params.merge!({:Authorization=>BASIC_AUTH})
+ end
+ RestClient.get API_URL+path, params
+end
+
+def post(post_body = "", path= "", params={}, authenticate = false)
+ if authenticate
+ params.merge!({:Authorization=>BASIC_AUTH})
+ end
+ RestClient.post API_URL+path, post_body, params
+end
+
+def delete(params={}, path = "", authenticate = true)
+ if authenticate
+ params.merge!({:Authorization=>BASIC_AUTH})
+ end
+ RestClient.delete API_URL+path, params
+end
+
+def options(params={}, path="", authenticate = false)
+ if authenticate
+ params.merge!({:Authorization=>BASIC_AUTH})
+ end
+ RestClient.options API_URL+path, params
+end
+
+#the TEST_FILES hash and deltacloud_test_file_names method
+#which follows is used in the Rakefile for the rake test:deltacloud task
+TEST_FILES = { :images => "images_test.rb",
+ :realms => "realms_test.rb",
+ :hardware_profiles => "hardware_profiles_test.rb",
+ :instance_states => "instance_states_test.rb",
+ :instances => "instances_test.rb",
+ :keys => "keys_test.rb",
+ :firewalls => "firewalls_test.rb",
+ :addresses => "addresses_test.rb",
+ :load_balancers => "load_balancers_test.rb",
+ :storage_volumes => "storage_volumes_test.rb",
+ :storage_snapshots => "storage_snapshots_test.rb",
+ :buckets => "buckets_test.rb"
+ }
+#gets the list of collections from the server running at API_URL and translates those into file names accoring to TEST_FILES
+def deltacloud_test_file_names
+ driver_collections = xml_response(RestClient.get API_URL, {:accept=>:xml}).xpath("//api/link").inject([]){|res, current| res<<current[:rel].to_sym ;res}
+ driver_collections.inject([]){|res, current| res << "deltacloud/#{TEST_FILES[current]}" if TEST_FILES[current] ;res}
+end
+
+def create_a_bucket_and_blob
+ #random bucket and blob name - make sure starts with letter:
+ bucket_name = random_name
+ blob_name = random_name
+ #create bucket:
+# res = RestClient.post "#{API_URL}/buckets", {:name=>bucket_name}, {:Authorization=>BASIC_AUTH}
+ res = post({:name=>bucket_name}, "/buckets", {}, true)
+ raise Exception.new("unable to create bucket with name #{bucket_name} for bucket_test.rb") unless res.code == 201
+ #create blob:
+ res = RestClient.put "#{API_URL}/buckets/#{bucket_name}/#{blob_name}", "This is the test blob content", {:Authorization=>BASIC_AUTH, :content_type=>"text/plain", "X-Deltacloud-Blobmeta-Version"=>"1.0", "X-Deltacloud-Blobmeta-Author"=>"herpyderp"}
+ raise Exception.new("unable to create blob with name #{blob_name} for bucket_test.rb") unless res.code == 200
+ return [bucket_name, blob_name]
+end
+
+def random_name
+ name = rand(36**10).to_s(36)
+ name.insert(0, "apitest")
+end
+
+def delete_bucket_and_blob(bucket, blob)
+# res = RestClient.delete "#{API_URL}/buckets/#{bucket}/#{blob}", {:Authorization=>BASIC_AUTH}
+ res = delete({}, "/buckets/#{bucket}/#{blob}")
+ raise Exception.new("unable to delete blob with name #{blob} for bucket_test.rb") unless res.code == 204
+# res = RestClient.delete "#{API_URL}/buckets/#{bucket}", {:Authorization=>BASIC_AUTH}
+ res = delete({}, "/buckets/#{bucket}")
+ raise Exception.new("unable to delete bucket with name #{bucket} for bucket_test.rb") unless res.code == 204
+end
+
+def discover_features
+ res = xml_response(get)
+ features_hash = res.xpath("//api/link").inject({}) do |result, collection|
+ result.merge!({collection[:rel] => []})
+ collection.children.inject([]){|features, current_child| result[collection[:rel]] << current_child[:name] if current_child.name == "feature"}
+ result
+ end
+end
--
1.7.6.5