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